Torc  0.1
torcwebsocket.cpp
Go to the documentation of this file.
1 /* Class TorcWebSocket
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2013-18
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
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.
16 *
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,
20 * USA.
21 */
22 
23 // Qt
24 #include <QUrl>
25 #include <QTimer>
26 #include <QTextStream>
27 #include <QJsonDocument>
28 #include <QCryptographicHash>
29 
30 // Torc
31 #include "torclogging.h"
32 #include "torcnetwork.h"
33 #include "torcnetworkedcontext.h"
34 #include "torchttprequest.h"
35 #include "torcrpcrequest.h"
36 #include "torchttpserver.h"
37 #include "torcwebsocket.h"
38 #include "torcupnp.h"
39 
62 TorcWebSocket::TorcWebSocket(TorcWebSocketThread* Parent, qintptr SocketDescriptor, bool Secure)
63  : QSslSocket(),
64  m_parent(Parent),
65  m_debug(),
66  m_secure(Secure),
67  m_socketState(SocketState::DisconnectedSt),
68  m_socketDescriptor(SocketDescriptor),
69  m_watchdogTimer(),
70  m_reader(),
71  m_wsReader(*this, TorcWebSocketReader::SubProtocolNone, true),
72  m_authenticated(false),
73  m_challengeResponse(),
74  m_address(QHostAddress()),
75  m_port(0),
76  m_serverSide(true),
77  m_subProtocol(TorcWebSocketReader::SubProtocolNone),
78  m_subProtocolFrameFormat(TorcWebSocketReader::OpText),
79  m_currentRequestID(1),
80  m_currentRequests(),
81  m_requestTimers(),
82  m_subscribers()
83 {
84  connect(&m_watchdogTimer, &QTimer::timeout, this, &TorcWebSocket::TimedOut);
85  m_watchdogTimer.start(HTTP_SOCKET_TIMEOUT);
86 }
87 
88 TorcWebSocket::TorcWebSocket(TorcWebSocketThread* Parent, const QHostAddress &Address, quint16 Port, bool Secure, TorcWebSocketReader::WSSubProtocol Protocol)
89  : QSslSocket(),
90  m_parent(Parent),
91  m_debug(),
92  m_secure(Secure),
93  m_socketState(SocketState::DisconnectedSt),
94  m_socketDescriptor(0),
95  m_watchdogTimer(),
96  m_reader(),
97  m_wsReader(*this, Protocol, false),
98  m_authenticated(false),
99  m_challengeResponse(),
100  m_address(Address),
101  m_port(Port),
102  m_serverSide(false),
103  m_subProtocol(Protocol),
104  m_subProtocolFrameFormat(TorcWebSocketReader::FormatForSubProtocol(Protocol)),
105  m_currentRequestID(1),
106  m_currentRequests(),
107  m_requestTimers(),
108  m_subscribers()
109 {
110  // NB outgoing connection - do not start watchdog timer
111 }
112 
114 {
115  // cancel any outstanding requests
116  if (!m_currentRequests.isEmpty())
117  {
118  // clients should be cancelling requests before closing the connection, so this
119  // may represent a leak
120  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("%1 outstanding RPC requests").arg(m_currentRequests.size()));
121 
122  while (!m_currentRequests.isEmpty())
123  CancelRequest(m_currentRequests.constBegin().value());
124  }
125 
126  if (m_socketState == SocketState::Upgraded)
127  m_wsReader.InitiateClose(TorcWebSocketReader::CloseGoingAway, QStringLiteral("WebSocket exiting normally"));
128 
129  CloseSocket();
130 }
131 
132 void TorcWebSocket::HandleUpgradeRequest(TorcHTTPRequest &Request)
133 {
134  if (!m_serverSide)
135  {
136  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to send response to upgrade request but not server side"));
137  SetState(SocketState::ErroredSt);
138  return;
139  }
140 
141  if (m_socketState != SocketState::ConnectedTo)
142  {
143  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to send response to upgrade request but not in connected state (HTTP)"));
144  SetState(SocketState::ErroredSt);
145  return;
146  }
147 
148  bool valid = true;
149  bool versionerror = false;
150  QString error;
152 
153  /* Excerpt from RFC 6455
154 
155  The requirements for this handshake are as follows.
156 
157  1. The handshake MUST be a valid HTTP request as specified by
158  [RFC2616].
159 
160  2. The method of the request MUST be GET, and the HTTP version MUST
161  be at least 1.1.
162 
163  For example, if the WebSocket URI is "ws://example.com/chat",
164  the first line sent should be "GET /chat HTTP/1.1".
165  */
166 
167  if (valid && (Request.GetHTTPRequestType() != HTTPGet || Request.GetHTTPProtocol() != HTTPOneDotOne))
168  {
169  error = QStringLiteral("Must be GET and HTTP/1.1");
170  valid = false;
171  }
172 
173  /*
174  3. The "Request-URI" part of the request MUST match the /resource
175  name/ defined in Section 3 (a relative URI) or be an absolute
176  http/https URI that, when parsed, has a /resource name/, /host/,
177  and /port/ that match the corresponding ws/wss URI.
178  */
179 
180  if (valid && Request.GetPath().isEmpty())
181  {
182  error = QStringLiteral("invalid Request-URI");
183  valid = false;
184  }
185 
186  /*
187  4. The request MUST contain a |Host| header field whose value
188  contains /host/ plus optionally ":" followed by /port/ (when not
189  using the default port).
190  */
191 
192  if (valid && !Request.Headers().contains(QStringLiteral("Host")))
193  {
194  error = QStringLiteral("No Host header");
195  valid = false;
196  }
197 
198  // default ports (e.g. 80) are not listed in host, so don't check in this case
199  // FIXME - should this be https as well? is this is the ios issue?(but check not used below)
200  QUrl host(QStringLiteral("http://%1").arg(Request.Headers().value(QStringLiteral("Host"))));
201  int localport = localPort();
202 
203  if (valid && localport != 80 && localport != 443 && host.port() != localport)
204  {
205  error = QStringLiteral("Invalid Host port");
206  valid = false;
207  }
208 
209  // disable host check. It offers us no additional security and may be a raw
210  // ip address, domain name or or other host name.
211 #if 0
212  if (valid && host.host() != localAddress().toString())
213  {
214  error = QStringLiteral("Invalid Host");
215  valid = false;
216  }
217 #endif
218  /*
219  5. The request MUST contain an |Upgrade| header field whose value
220  MUST include the "websocket" keyword.
221  */
222 
223  if (valid && !Request.Headers().contains(QStringLiteral("Upgrade")))
224  {
225  error = QStringLiteral("No Upgrade header");
226  valid = false;
227  }
228 
229  if (valid && !Request.Headers().value(QStringLiteral("Upgrade")).contains(QStringLiteral("websocket"), Qt::CaseInsensitive))
230  {
231  error = QStringLiteral("No 'websocket request");
232  valid = false;
233  }
234 
235  /*
236  6. The request MUST contain a |Connection| header field whose value
237  MUST include the "Upgrade" token.
238  */
239 
240  if (valid && !Request.Headers().contains(QStringLiteral("Connection")))
241  {
242  error = QStringLiteral("No Connection header");
243  valid = false;
244  }
245 
246  if (valid && !Request.Headers().value(QStringLiteral("Connection")).contains(QStringLiteral("Upgrade"), Qt::CaseInsensitive))
247  {
248  error = QStringLiteral("No Upgrade request");
249  valid = false;
250  }
251 
252  /*
253  7. The request MUST include a header field with the name
254  |Sec-WebSocket-Key|. The value of this header field MUST be a
255  nonce consisting of a randomly selected 16-byte value that has
256  been base64-encoded (see Section 4 of [RFC4648]). The nonce
257  MUST be selected randomly for each connection.
258 
259  NOTE: As an example, if the randomly selected value was the
260  sequence of bytes 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09
261  0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10, the value of the header
262  field would be "AQIDBAUGBwgJCgsMDQ4PEC=="
263  */
264 
265  if (valid && !Request.Headers().contains(QStringLiteral("Sec-WebSocket-Key")))
266  {
267  error = QStringLiteral("No Sec-WebSocket-Key header");
268  valid = false;
269  }
270 
271  if (valid && Request.Headers().value(QStringLiteral("Sec-WebSocket-Key")).isEmpty())
272  {
273  error = QStringLiteral("Invalid Sec-WebSocket-Key");
274  valid = false;
275  }
276 
277  /*
278  8. The request MUST include a header field with the name |Origin|
279  [RFC6454] if the request is coming from a browser client. If
280  the connection is from a non-browser client, the request MAY
281  include this header field if the semantics of that client match
282  the use-case described here for browser clients. The value of
283  this header field is the ASCII serialization of origin of the
284  context in which the code establishing the connection is
285  running. See [RFC6454] for the details of how this header field
286  value is constructed.
287 
288  As an example, if code downloaded from www.example.com attempts
289  to establish a connection to ww2.example.com, the value of the
290  header field would be "http://www.example.com".
291 
292  9. The request MUST include a header field with the name
293  |Sec-WebSocket-Version|. The value of this header field MUST be
294  13.
295  */
296 
297  if (valid && !Request.Headers().contains(QStringLiteral("Sec-WebSocket-Version")))
298  {
299  error = QStringLiteral("No Sec-WebSocket-Version header");
300  valid = false;
301  }
302 
303  int version = Request.Headers().value(QStringLiteral("Sec-WebSocket-Version")).toInt();
304 
305  if (valid && version != TorcWebSocketReader::Version13 && version != TorcWebSocketReader::Version8)
306  {
307  error = QStringLiteral("Unsupported WebSocket version");
308  versionerror = true;
309  valid = false;
310  }
311 
312  /*
313  10. The request MAY include a header field with the name
314  |Sec-WebSocket-Protocol|. If present, this value indicates one
315  or more comma-separated subprotocol the client wishes to speak,
316  ordered by preference. The elements that comprise this value
317  MUST be non-empty strings with characters in the range U+0021 to
318  U+007E not including separator characters as defined in
319  [RFC2616] and MUST all be unique strings. The ABNF for the
320  value of this header field is 1#token, where the definitions of
321  constructs and rules are as given in [RFC2616].
322  */
323 
324  if (Request.Headers().contains(QStringLiteral("Sec-WebSocket-Protocol")))
325  {
326  QList<TorcWebSocketReader::WSSubProtocol> protocols = TorcWebSocketReader::SubProtocolsFromPrioritisedString(Request.Headers().value(QStringLiteral("Sec-WebSocket-Protocol")));
327  if (!protocols.isEmpty())
328  protocol = protocols.first();
329  }
330 
331  if (!valid)
332  {
333  LOG(VB_GENERAL, LOG_ERR, error);
334  Request.SetStatus(HTTP_BadRequest);
335  if (versionerror)
336  Request.SetResponseHeader(QStringLiteral("Sec-WebSocket-Version"), QStringLiteral("8,13"));
337  return;
338  }
339 
340  // valid handshake so set response headers and transfer socket
341  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Received valid websocket Upgrade request"));
342 
343  QString key = QStringLiteral("%1%2").arg(Request.Headers().value(QStringLiteral("Sec-WebSocket-Key")), QStringLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
344  QString nonce = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toBase64();
345 
349  Request.SetResponseHeader(QStringLiteral("Upgrade"), QStringLiteral("websocket"));
350  Request.SetResponseHeader(QStringLiteral("Sec-WebSocket-Accept"), nonce);
351  if (protocol != TorcWebSocketReader::SubProtocolNone)
352  {
353  Request.SetResponseHeader(QStringLiteral("Sec-WebSocket-Protocol"), TorcWebSocketReader::SubProtocolsToString(protocol));
354  m_subProtocol = protocol;
355  m_subProtocolFrameFormat = TorcWebSocketReader::FormatForSubProtocol(protocol);
356  m_wsReader.SetSubProtocol(protocol);
357  }
358 
359  SetState(SocketState::Upgraded);
360  m_authenticated = Request.IsAuthorised();
361 
362  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 socket upgraded (%2)").arg(m_debug, m_authenticated ? QStringLiteral("Authenticated") : QStringLiteral("Unauthenticated")));
363 
364  if (Request.Headers().contains(QStringLiteral("Torc-UUID")))
365  {
366  // stop the watchdog timer for peers
367  m_watchdogTimer.stop();
368  QVariantMap data;
369  data.insert(TORC_UUID, Request.Headers().value(QStringLiteral("Torc-UUID")));
370  data.insert(TORC_NAME, Request.Headers().value(QStringLiteral("Torc-Name")));
371  data.insert(TORC_PORT, Request.Headers().value(QStringLiteral("Torc-Port")));
372  data.insert(TORC_PRIORITY, Request.Headers().value(QStringLiteral("Torc-Priority")));
373  data.insert(TORC_STARTTIME, Request.Headers().value(QStringLiteral("Torc-Starttime")));
374  data.insert(TORC_APIVERSION, Request.Headers().value(QStringLiteral("Torc-APIVersion")));
375  data.insert(TORC_ADDRESS, peerAddress().toString());
376  if (Request.Headers().contains(QStringLiteral("Torc-Secure")))
377  data.insert(TORC_SECURE, TORC_YES);
378  TorcNetworkedContext::PeerConnected(m_parent, data);
379  }
380  else
381  {
382  // increase the timeout for upgraded sockets
383  m_watchdogTimer.start(FULL_SOCKET_TIMEOUT);
384  }
385 
386  if (Request.GetMethod().startsWith(QStringLiteral("echo"), Qt::CaseInsensitive))
387  {
388  m_wsReader.EnableEcho();
389  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Enabling WebSocket echo for testing"));
390  }
391 }
392 
395 {
396  QVariantList result;
397  QVariantMap proto;
398  proto.insert(TORC_NAME, TORC_JSON_RPC);
399  proto.insert(QStringLiteral("description"), QStringLiteral("I can't remember how this differs from straight JSON-RPC:) The overall mechanism is very similar to WAMP."));
400  result.append(proto);
401  return result;
402 }
403 
405 {
406  if (m_debug.isEmpty())
407  m_debug = QStringLiteral(">> %1 %2 -").arg(TorcNetwork::IPAddressToLiteral(peerAddress(), peerPort()), m_secure ? TORC_SECURE : QStringLiteral("insecure"));
408  connect(this, &TorcWebSocket::readyRead, this, &TorcWebSocket::ReadyRead);
409  emit ConnectionEstablished();
410  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 encrypted").arg(m_debug));
411 
412  if (!m_serverSide)
413  Connected();
414 }
415 
416 void TorcWebSocket::SSLErrors(const QList<QSslError> &Errors)
417 {
418  QList<QSslError> allowed = TorcNetwork::AllowableSslErrors(Errors);
419  if (!allowed.isEmpty())
420  ignoreSslErrors(allowed);
421 }
422 
424 {
425  return m_secure;
426 }
427 
430 {
432  connect(this, &TorcWebSocket::bytesWritten, this, &TorcWebSocket::BytesWritten);
433 
434  // common setup
435  m_reader.Reset();
436  m_wsReader.Reset();
437 
438  connect(this, static_cast<void (TorcWebSocket::*)(QAbstractSocket::SocketError)>(&TorcWebSocket::error), this, &TorcWebSocket::Error);
439  connect(this, &TorcWebSocket::disconnected, this, &TorcWebSocket::Disconnected);
440  connect(this, &TorcWebSocket::encrypted, this, &TorcWebSocket::Encrypted);
441  connect(this, static_cast<void (TorcWebSocket::*)(const QList<QSslError>&)>(&TorcWebSocket::sslErrors), this, &TorcWebSocket::SSLErrors);
442 
443  // Ignore errors for Self signed certificates
444  QList<QSslError> ignore;
445  ignore << QSslError::SelfSignedCertificate;
446  ignoreSslErrors(ignore);
447 
448  // server side:)
449  if (m_serverSide && m_socketDescriptor)
450  {
451  if (setSocketDescriptor(m_socketDescriptor))
452  {
453  m_debug = QStringLiteral("<< %1 %2 -").arg(TorcNetwork::IPAddressToLiteral(peerAddress(), peerPort()), m_secure ? TORC_SECURE : QStringLiteral("insecure"));
454  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 socket connected (%2)").arg(m_debug, m_authenticated ? QStringLiteral("Authenticated") : QStringLiteral("Unauthenticated")));
455  SetState(SocketState::ConnectedTo);
456 
457  if (m_secure)
458  {
459  startServerEncryption();
460  }
461  else
462  {
463  connect(this, &TorcWebSocket::readyRead, this, &TorcWebSocket::ReadyRead);
464  emit ConnectionEstablished();
465  }
466  return;
467  }
468  else
469  {
470  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Failed to set socket descriptor"));
471  }
472  }
473  else if (!m_serverSide)
474  {
475  SetState(SocketState::ConnectingTo);
476 
477  if (m_secure)
478  {
479  connectToHostEncrypted(m_address.toString(), m_port);
480  }
481  else
482  {
483  connect(this, &TorcWebSocket::connected, this, &TorcWebSocket::Connected);
484  connect(this, &TorcWebSocket::readyRead, this, &TorcWebSocket::ReadyRead);
485  connectToHost(m_address, m_port);
486  }
487  return;
488  }
489 
490  // failed
491  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to start Websocket"));
492  SetState(SocketState::ErroredSt);
493 }
494 
497 {
498  TorcHTTPService *service = dynamic_cast<TorcHTTPService*>(sender());
499  if (service && senderSignalIndex() > -1)
500  {
501  TorcRPCRequest *request = new TorcRPCRequest(service->Signature() + service->GetMethod(senderSignalIndex()));
502  request->AddParameter(QStringLiteral("value"), service->GetProperty(senderSignalIndex()));
503  m_wsReader.SendFrame(m_subProtocolFrameFormat, request->SerialiseRequest(m_subProtocol));
504  request->DownRef();
505  }
506 }
507 
508 bool TorcWebSocket::HandleNotification(const QString &Method)
509 {
510  if (m_subscribers.contains(Method))
511  {
512  QMap<QString,QObject*>::const_iterator it = m_subscribers.constFind(Method);
513  while (it != m_subscribers.constEnd() && it.key() == Method)
514  {
515  QMetaObject::invokeMethod(it.value(), "ServiceNotification", Q_ARG(QString, Method));
516  ++it;
517  }
518 
519  return true;
520  }
521 
522  return false;
523 }
524 
530 {
531  if (!Request)
532  return;
533 
534  bool notification = Request->IsNotification();
535 
536  // NB notitications cannot be cancelled - they are fire and forget.
537  // NB other requests cannot be cancelled before this call is processed (they will not be present in m_currentRequests)
538  if (!notification)
539  {
540  Request->UpRef();
541  int id = m_currentRequestID++;
542  while (m_currentRequests.contains(id))
543  id = m_currentRequestID++;
544 
545  Request->SetID(id);
546  m_currentRequests.insert(id, Request);
547 
548  // start a timer for this request
549  m_requestTimers.insert(startTimer(10000, Qt::CoarseTimer), id);
550 
551  // keep id's at sane values
552  if (m_currentRequestID > 100000)
553  m_currentRequestID = 1;
554  }
555 
557 
558  if (m_subProtocol != TorcWebSocketReader::SubProtocolNone)
559  m_wsReader.SendFrame(m_subProtocolFrameFormat, Request->SerialiseRequest(m_subProtocol));
560  else
561  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("No protocol specified for remote procedure call"));
562 
563  // notifications are fire and forget, so downref immediately
564  if (notification)
565  Request->DownRef();
566 }
567 
573 {
574  if (Request && !Request->IsNotification() && Request->GetID() > -1)
575  {
577  int id = Request->GetID();
578  if (m_currentRequests.contains(id))
579  {
580  // cancel the timer
581  int timer = m_requestTimers.key(id);
582  killTimer(timer);
583  m_requestTimers.remove(timer);
584 
585  // cancel the request
586  m_currentRequests.remove(id);
587  Request->DownRef();
588  }
589  else
590  {
591  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot cancel unknown RPC request"));
592  }
593 
594  if (Request->IsShared())
595  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("RPC request is still referenced after cancellation - potential leak"));
596  }
597 }
598 
599 void TorcWebSocket::ReadHTTP(void)
600 {
601  if (!m_serverSide)
602  {
603  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to read HTTP but not server side"));
604  SetState(SocketState::ErroredSt);
605  return;
606  }
607 
608  if (m_socketState != SocketState::ConnectedTo)
609  {
610  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to read HTTP but not in connected state (raw HTTP)"));
611  SetState(SocketState::ErroredSt);
612  return;
613  }
614 
615  while (canReadLine())
616  {
617  // read data
618  if (!m_reader.Read(this))
619  {
620  SetState(SocketState::ErroredSt);
621  break;
622  }
623 
624  if (!m_reader.IsReady())
625  continue;
626 
627  // sanity check
628  if (bytesAvailable() > 0)
629  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("%1 unread bytes from %2").arg(bytesAvailable()).arg(peerAddress().toString()));
630 
631  // have headers and content - process request
632  TorcHTTPRequest request(&m_reader);
633  request.SetSecure(m_secure);
634 
635  if (request.GetHTTPType() == HTTPResponse)
636  {
637  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Received unexpected HTTP response"));
638  SetState(SocketState::ErroredSt);
639  return;
640  }
641 
642  bool upgrade = request.Headers().contains(QStringLiteral("Upgrade"));
643  TorcHTTPServer::Authorise(peerAddress().toString(), request, upgrade);
644 
645  if (upgrade)
646  {
647  HandleUpgradeRequest(request);
648  }
649  else
650  {
651  if (request.IsAuthorised() == HTTPAuthorised || request.IsAuthorised() == HTTPPreAuthorised)
652  {
653  TorcHTTPServer::HandleRequest(peerAddress().toString(), peerPort(),
654  localAddress().toString(), localPort(), request);
655  }
656  }
657  request.Respond(this);
658 
659  // reset
660  m_reader.Reset();
661  }
662 }
669 {
670  // restart the watchdog timer
671  if (m_watchdogTimer.isActive())
672  m_watchdogTimer.start();
673 
674  // Guard against spurious socket activity/connections e.g. a client trying to connect using SSL when
675  // the server is not expecting secure sockets. TorcHTTPReader will be expecting a complete line but no additional
676  // data is received so we loop continuously. So count retries while the available bytes remains unchanged and introduce
677  // a micro sleep when available bytes does not change during the loop.
678  static const int maxUnchanged = 5000;
679  static const int unchangedSleep = 1000; // microseconds (1ms) - for a total timeout of 5 seconds
680  int unchangedCount = 0;
681 
682  while ((m_socketState == SocketState::ConnectedTo || m_socketState == SocketState::Upgrading || m_socketState == SocketState::Upgraded) &&
683  bytesAvailable())
684  {
685  qint64 available = bytesAvailable();
686 
687  if (m_socketState == SocketState::ConnectedTo)
688  {
689  ReadHTTP();
690  }
691 
692  // we may now be upgrading
693  if (m_socketState == SocketState::Upgrading)
694  {
695  if (m_serverSide)
696  {
697  // the upgrade request is handled in ReadHTTP and if successfully processed we
698  // move directly into the Upgraded state
699  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Upgrading state on server side"));
700  SetState(SocketState::ErroredSt);
701  return;
702  }
703  else
704  {
705  ReadHandshake();
706  }
707  }
708 
709  // we may now be upgraded
710  if (m_socketState == SocketState::Upgraded)
711  {
712  if (!m_wsReader.Read())
713  {
714  if (m_wsReader.CloseSent())
715  SetState(SocketState::Disconnecting);
716  }
717  else
718  {
719  // have a payload
720  ProcessPayload(m_wsReader.GetPayload());
721  m_wsReader.Reset();
722  }
723  }
724 
725  // pause if necessary
726  if (bytesAvailable() == available)
727  {
728  unchangedCount++;
729  if (unchangedCount > maxUnchanged)
730  {
731  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Socket time out waiting for valid data - closing"));
732  SetState(SocketState::Disconnecting);
733  break;
734  }
735  TorcQThread::usleep(unchangedSleep);
736  }
737  }
738 }
739 
745 void TorcWebSocket::SubscriberDeleted(QObject *Object)
746 {
747  QList<QString> remove;
748 
749  QMap<QString,QObject*>::const_iterator it = m_subscribers.constBegin();
750  for ( ; it != m_subscribers.constEnd(); ++it)
751  if (it.value() == Object)
752  remove.append(it.key());
753 
754  foreach (const QString &service, remove)
755  {
756  m_subscribers.remove(service, Object);
757  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Removed stale subscription to '%1'").arg(service));
758  }
759 }
760 
762 {
763  if(state() == QAbstractSocket::ConnectedState)
764  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 disconnecting").arg(m_debug));
765 
766  disconnectFromHost();
767  // we only check for connected state - we don't care if it is any in any prior or subsequent state (hostlookup, connecting) and
768  // the wait is only a 'courtesy' anyway.
769  if (state() == QAbstractSocket::ConnectedState && !waitForDisconnected(1000))
770  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("WebSocket not successfully disconnected before closing"));
771  close();
772 }
773 
775 {
776  m_debug = QStringLiteral(">> %1 %2 -").arg(TorcNetwork::IPAddressToLiteral(peerAddress(), peerPort()), m_secure ? TORC_SECURE : QStringLiteral("insecure"));
777  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 connection to remote host").arg(m_debug));
778  SetState(SocketState::Upgrading);
779  SendHandshake();
780 }
781 
782 void TorcWebSocket::SendHandshake(void)
783 {
784  if (m_serverSide)
785  {
786  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to send handshake from server side"));
787  SetState(SocketState::ErroredSt);
788  return;
789  }
790 
791  if (m_socketState != SocketState::Upgrading)
792  {
793  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to upgrade from incorrect state (client side)"));
794  SetState(SocketState::ErroredSt);
795  return;
796  }
797 
798  QScopedPointer<QByteArray> upgrade(new QByteArray());
799  QTextStream stream(upgrade.data());
800 
801  QByteArray nonce;
802  for (int i = 0; i < 16; ++i)
803  nonce.append(qrand() % 0x100);
804  nonce = nonce.toBase64();
805 
806  // store the expected response for later comparison
807  QString key = QStringLiteral("%1%2").arg(nonce.data(), QStringLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
808  m_challengeResponse = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toBase64();
809 
810  QHostAddress host(m_address);
812 
813  stream << "GET / HTTP/1.1\r\n";
814  stream << "User-Agent: " << TorcHTTPServer::PlatformName() << "\r\n";
815  stream << "Host: " << TorcNetwork::IPAddressToLiteral(host, m_port) << "\r\n";
816  stream << "Upgrade: WebSocket\r\n";
817  stream << "Connection: Upgrade\r\n";
818  stream << "Sec-WebSocket-Version: 13\r\n";
819  stream << "Sec-WebSocket-Key: " << nonce.data() << "\r\n";
820  if (m_subProtocol != TorcWebSocketReader::SubProtocolNone)
821  stream << "Sec-WebSocket-Protocol: " << TorcWebSocketReader::SubProtocolsToString(m_subProtocol) << "\r\n";
822  stream << "Torc-UUID: " << gLocalContext->GetUuid() << "\r\n";
823  stream << "Torc-Port: " << QString::number(server.port) << "\r\n";
824  stream << "Torc-Name: " << TorcHTTPServer::ServerDescription() << "\r\n";
825  stream << "Torc-Priority:" << gLocalContext->GetPriority() << "\r\n";
826  stream << "Torc-Starttime:" << gLocalContext->GetStartTime() << "\r\n";
827  stream << "Torc-APIVersion:" << TorcHTTPServices::GetVersion() << "\r\n";
828  if (server.secure)
829  stream << "Torc-Secure: yes\r\n";
830  stream << "\r\n";
831  stream.flush();
832 
833  if (write(upgrade->data(), upgrade->size()) != upgrade->size())
834  {
835  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unexpected write error"));
836  SetState(SocketState::ErroredSt);
837  return;
838  }
839 
840  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("%1 client WebSocket connected (SubProtocol: %2)")
841  .arg(m_debug, TorcWebSocketReader::SubProtocolsToString(m_subProtocol)));
842 
843  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Data...\r\n%1").arg(upgrade->data()));
844 }
845 
846 void TorcWebSocket::ReadHandshake(void)
847 {
848  if (m_serverSide)
849  {
850  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to read handshake server side"));
851  SetState(SocketState::ErroredSt);
852  return;
853  }
854 
855  if (m_socketState != SocketState::Upgrading)
856  {
857  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Trying to read handshake but not upgrading"));
858  SetState(SocketState::ErroredSt);
859  return;
860  }
861 
862  // read response (which is the only raw HTTP we should see)
863  if (!m_reader.Read(this))
864  {
865  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error reading upgrade response"));
866  SetState(SocketState::ErroredSt);
867  return;
868  }
869 
870  // response complete
871  if (!m_reader.IsReady())
872  return;
873 
874  // parse the response
875  TorcHTTPRequest request(&m_reader);
876 
877  bool valid = true;
878  QString error;
879 
880  // is it a response
881  if (valid && request.GetHTTPType() != HTTPResponse)
882  {
883  valid = false;
884  error = QStringLiteral("Response is not an HTTP response");
885  }
886 
887  // is it switching protocols
888  if (valid && request.GetHTTPStatus() != HTTP_SwitchingProtocols)
889  {
890  valid = false;
891  error = QStringLiteral("Expected '%1' - got '%2'").arg(TorcHTTPRequest::StatusToString(HTTP_SwitchingProtocols),
893  }
894 
895  // does it contain the correct headers
896  if (valid && !(request.Headers().contains(QStringLiteral("Upgrade")) && request.Headers().contains(QStringLiteral("Connection")) &&
897  request.Headers().contains(QStringLiteral("Sec-WebSocket-Accept"))))
898  {
899  valid = false;
900  error = QStringLiteral("Response is missing required headers");
901  }
902 
903  // correct header contents
904  if (valid)
905  {
906  QString upgrade = request.Headers().value(QStringLiteral("Upgrade")).trimmed();
907  QString connection = request.Headers().value(QStringLiteral("Connection")).trimmed();
908  QString accept = request.Headers().value(QStringLiteral("Sec-WebSocket-Accept")).trimmed();
909  QString protocols = request.Headers().value(QStringLiteral("Sec-WebSocket-Protocol")).trimmed();
910 
911  if (!upgrade.contains(QStringLiteral("websocket"), Qt::CaseInsensitive) || !connection.contains(QStringLiteral("upgrade"), Qt::CaseInsensitive))
912  {
913  valid = false;
914  error = QStringLiteral("Unexpected header content");
915  }
916  else
917  {
918  if (!accept.contains(m_challengeResponse, Qt::CaseSensitive))
919  {
920  valid = false;
921  error = QStringLiteral("Incorrect Sec-WebSocket-Accept response");
922  }
923  }
924 
925  // ensure the correct subprotocol (if any) has been agreed
926  if (valid)
927  {
928  if (m_subProtocol == TorcWebSocketReader::SubProtocolNone)
929  {
930  if (!protocols.isEmpty())
931  {
932  valid = false;
933  error = QStringLiteral("Unexpected subprotocol");
934  }
935  }
936  else
937  {
938  TorcWebSocketReader::WSSubProtocols subprotocols = TorcWebSocketReader::SubProtocolsFromString(protocols);
939  if ((subprotocols | m_subProtocol) != m_subProtocol)
940  {
941  valid = false;
942  error = QStringLiteral("Unexpected subprotocol");
943  }
944  }
945  }
946  }
947 
948  if (!valid)
949  {
950  LOG(VB_GENERAL, LOG_ERR, error);
951  SetState(SocketState::ErroredSt);
952  return;
953  }
954 
955  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Received valid upgrade response - switching to frame protocol"));
956  SetState(SocketState::Upgraded);
957 }
958 
959 void TorcWebSocket::Error(QAbstractSocket::SocketError SocketError)
960 {
961  if (QAbstractSocket::RemoteHostClosedError == SocketError)
962  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 remote host disconnected socket").arg(m_debug));
963  else
964  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 socket error %2 '%3'").arg(m_debug).arg(SocketError).arg(errorString()));
965  SetState(SocketState::ErroredSt);
966 }
967 
969 {
970  if(m_socketState == SocketState::Upgraded)
971  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("%1 no websocket traffic for %2seconds").arg(m_debug).arg(m_watchdogTimer.interval() / 1000));
972  else
973  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("%1 no HTTP traffic for %2seconds").arg(m_debug).arg(m_watchdogTimer.interval() / 1000));
974  SetState(SocketState::DisconnectedSt);
975 }
976 
978 {
979  if (m_watchdogTimer.isActive())
980  m_watchdogTimer.start();
981 }
982 
983 bool TorcWebSocket::event(QEvent *Event)
984 {
985  if (Event->type() == QEvent::Timer)
986  {
987  QTimerEvent *event = static_cast<QTimerEvent*>(Event);
988  if (event)
989  {
990  int timerid = event->timerId();
991 
992  if (m_requestTimers.contains(timerid))
993  {
994  // remove the timer
995  int requestid = m_requestTimers.value(timerid);
996  killTimer(timerid);
997  m_requestTimers.remove(timerid);
998 
999  // handle the timeout
1000  TorcRPCRequest *request = nullptr;
1001  if (m_currentRequests.contains(requestid) && (request = m_currentRequests.value(requestid)))
1002  {
1004  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("'%1' request timed out").arg(request->GetMethod()));
1005 
1006  request->NotifyParent();
1007 
1008  m_currentRequests.remove(requestid);
1009  request->DownRef();
1010  }
1011 
1012  return true;
1013  }
1014  }
1015  }
1016 
1017  return QSslSocket::event(Event);
1018 }
1019 
1020 void TorcWebSocket::SetState(SocketState State)
1021 {
1022  if (State == m_socketState)
1023  return;
1024 
1025  m_socketState = State;
1026 
1027  if (m_socketState == SocketState::Disconnecting || m_socketState == SocketState::DisconnectedSt ||
1028  m_socketState == SocketState::ErroredSt)
1029  {
1030  emit Disconnected();
1031  }
1032 
1033  if (m_socketState == SocketState::Upgraded)
1034  emit ConnectionUpgraded();
1035 }
1036 
1037 void TorcWebSocket::ProcessPayload(const QByteArray &Payload)
1038 {
1039  if (m_subProtocol == TorcWebSocketReader::SubProtocolJSONRPC)
1040  {
1041  // NB there is no method to support SENDING batched requests (hence
1042  // we should only receive batched requests from 3rd parties) and hence there
1043  // is no support for handling batched responses.
1044  TorcRPCRequest *request = new TorcRPCRequest(m_subProtocol, Payload, this, m_authenticated);
1045 
1046  // if the request has data, we need to send it (it was a request!)
1047  if (!request->GetData().isEmpty())
1048  {
1049  m_wsReader.SendFrame(m_subProtocolFrameFormat, request->GetData());
1050  }
1051  // if the request has an id, we need to process it
1052  else if (request->GetID() > -1)
1053  {
1054  int id = request->GetID();
1055  TorcRPCRequest *requestor = nullptr;
1056  if (m_currentRequests.contains(id) && (requestor = m_currentRequests.value(id)))
1057  {
1059 
1060  if (request->GetState() & TorcRPCRequest::Errored)
1061  {
1062  requestor->AddState(TorcRPCRequest::Errored);
1063  }
1064  else
1065  {
1066  QString method = requestor->GetMethod();
1067  // is this a successful response to a subscription request?
1068  if (method.endsWith(QStringLiteral("/Subscribe")))
1069  {
1070  method.chop(9);
1071  QObject *parent = requestor->GetParent();
1072 
1073  if (parent->metaObject()->indexOfSlot(QMetaObject::normalizedSignature("ServiceNotification(QString)")) < 0)
1074  {
1075  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot monitor subscription to '%1' for object '%2' - no notification slot").arg(method, parent->objectName()));
1076  }
1077  else if (request->GetReply().type() == QVariant::Map)
1078  {
1079  // listen for destroyed signals to ensure the subscriptions are cleaned up
1080  connect(parent, &QObject::destroyed, this, &TorcWebSocket::SubscriberDeleted);
1081 
1082  QVariantMap map = request->GetReply().toMap();
1083  if (map.contains(QStringLiteral("properties")) && map.value(QStringLiteral("properties")).type() == QVariant::List)
1084  {
1085  QVariantList properties = map.value(QStringLiteral("properties")).toList();
1086 
1087  // add each notification/parent pair to the subscriber list
1088  QVariantList::const_iterator it = properties.constBegin();
1089  for ( ; it != properties.constEnd(); ++it)
1090  {
1091  if (it->type() == QVariant::Map)
1092  {
1093  QVariantMap property = it->toMap();
1094  if (property.contains(QStringLiteral("notification")))
1095  {
1096  QString service = method + property.value(QStringLiteral("notification")).toString();
1097  if (m_subscribers.contains(service, parent))
1098  {
1099  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Object '%1' already has subscription to '%2'").arg(parent->objectName(), service));
1100  }
1101  else
1102  {
1103  m_subscribers.insertMulti(service, parent);
1104  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Object '%1' subscribed to '%2'").arg(parent->objectName(), service));
1105  }
1106  }
1107  }
1108  }
1109  }
1110  }
1111  }
1112  // or a successful unsubscribe?
1113  else if (method.endsWith(QStringLiteral("/Unsubscribe")))
1114  {
1115  method.chop(11);
1116  QObject *parent = requestor->GetParent();
1117 
1118  // iterate over our subscriber list and remove anything that starts with method and points to parent
1119  QStringList remove;
1120 
1121  QMap<QString,QObject*>::const_iterator it = m_subscribers.constBegin();
1122  for ( ; it != m_subscribers.constEnd(); ++it)
1123  if (it.value() == parent && it.key().startsWith(method))
1124  remove.append(it.key());
1125 
1126  foreach (const QString &signature, remove)
1127  {
1128  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Object '%1' unsubscribed from '%2'").arg(parent->objectName(), signature));
1129  m_subscribers.remove(signature, parent);
1130  }
1131 
1132  // and disconnect the destroyed signal if we have no more subscriptions for this object
1133  if (std::find(m_subscribers.cbegin(), m_subscribers.cend(), parent) == m_subscribers.cend())
1134  {
1135  // temporary logging to ensure clazy optimisation is working correctly
1136  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("'%1' disconnect - no more subscriptions").arg(parent->objectName()));
1137  (void)disconnect(parent, nullptr, this, nullptr);
1138  }
1139  }
1140 
1141  requestor->SetReply(request->GetReply());
1142  }
1143 
1144  requestor->NotifyParent();
1145  m_currentRequests.remove(id);
1146  requestor->DownRef();
1147  }
1148  }
1149 
1150  request->DownRef();
1151  }
1152 }
void BytesWritten(qint64)
void SetSubProtocol(WSSubProtocol Protocol)
#define TORC_PRIORITY
Definition: torcupnp.h:19
static QList< WSSubProtocol > SubProtocolsFromPrioritisedString(const QString &Protocols)
Parse a prioritised list of supported WebSocket sub-protocols.
A class to encapsulate an incoming HTTP request.
HTTPRequestType GetHTTPRequestType(void) const
void SubscriberDeleted(QObject *Subscriber)
A subscriber object has been deleted.
const QByteArray & GetPayload(void)
bool Read(QTcpSocket *Socket)
Read and parse data from the given socket.
void Connected(void)
Overlays the Websocket protocol over a QTcpSocket.
Definition: torcwebsocket.h:23
TorcLocalContext * gLocalContext
bool IsReady(void) const
void SetConnection(HTTPConnection Connection)
static void Authorise(const QString &Host, TorcHTTPRequest &Request, bool ForceCheck)
Ensures remote user is authorised to access this request.
void ConnectionUpgraded(void)
QString Signature(void) const
HTTPStatus GetHTTPStatus(void) const
static QString SubProtocolsToString(WSSubProtocols Protocols)
Convert SubProtocols to HTTP readable string.
bool IsSecure(void)
QByteArray & GetData(void)
static QString StatusToString(HTTPStatus Status)
#define TORC_STARTTIME
Definition: torcupnp.h:20
int GetID(void) const
bool HandleNotification(const QString &Method)
static void HandleRequest(const QString &PeerAddress, int PeerPort, const QString &LocalAddress, int LocalPort, TorcHTTPRequest &Request)
QString GetUuid(void) const
static QString IPAddressToLiteral(const QHostAddress &Address, int Port, bool UseLocalhost=true)
Convert an IP address to a string literal.
void Disconnect(void)
void CancelRequest(TorcRPCRequest *Request)
Cancel a Remote Procedure Call.
virtual bool DownRef(void)
HTTPAuthorisation IsAuthorised(void) const
void AddState(int State)
Progress the state for this request.
#define TORC_APIVERSION
Definition: torcupnp.h:18
#define TORC_PORT
Definition: torcupnp.h:17
void SendFrame(OpCode Code, QByteArray &Payload)
A class encapsulating a Remote Procedure Call.
void NotifyParent(void)
Signal to the parent that the request is ready (but may be errored).
static QList< QSslError > AllowableSslErrors(const QList< QSslError > &Errors)
#define TORC_YES
Definition: torcupnp.h:24
static void PeerConnected(TorcWebSocketThread *Thread, const QVariantMap &Data)
Respond to a valid WebSocket upgrade request and schedule creation of a WebSocket on the give QTcpSoc...
QString GetMethod(void) const
TorcWebSocket(TorcWebSocketThread *Parent, qintptr SocketDescriptor, bool Secure)
void SSLErrors(const QList< QSslError > &Errors)
#define TORC_SECURE
Definition: torcupnp.h:22
QString GetPath(void) const
const QMap< QString, QString > & Headers(void) const
VERBOSE_PREAMBLE true
QString GetMethod(void) const
#define TORC_ADDRESS
Definition: torcupnp.h:21
int GetState(void) const
void CloseSocket(void)
void Disconnected(void)
static Status GetStatus(void)
#define FULL_SOCKET_TIMEOUT
Definition: torcwebsocket.h:21
bool event(QEvent *Event) override
#define HTTP_SOCKET_TIMEOUT
Definition: torcwebsocket.h:20
bool IsNotification(void) const
void SetResponseHeader(const QString &Header, const QString &Value)
QByteArray & SerialiseRequest(TorcWebSocketReader::WSSubProtocol Protocol)
Serialise the request for the given protocol.
static WSSubProtocols SubProtocolsFromString(const QString &Protocols)
Parse supported WSSubProtocols from Protocols.
const QVariant & GetReply(void) const
static QVariantList GetSupportedSubProtocols(void)
Return a list of supported WebSocket sub-protocols.
void Encrypted(void)
static QString GetVersion(void)
void SetSecure(bool Secure)
void SetStatus(HTTPStatus Status)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
static QString PlatformName(void)
QVariant GetProperty(int Index)
Get the value of a given property.
QString GetMethod(int Index)
void InitiateClose(CloseCode Close, const QString &Reason)
void ReadyRead(void)
Process incoming data.
#define TORC_UUID
Definition: torcupnp.h:15
void SetReply(const QVariant &Reply)
void SetResponseType(HTTPResponseType Type)
Wraps a TorcQThread around a TorcWebsocket.
void Error(QAbstractSocket::SocketError)
qint64 GetStartTime(void)
HTTPProtocol GetHTTPProtocol(void) const
static QString ServerDescription(void)
static OpCode FormatForSubProtocol(WSSubProtocol Protocol)
HTTPType GetHTTPType(void) const
QObject * GetParent(void) const
void TimedOut(void)
void PropertyChanged(void)
Receives notifications when a property for a subscribed service has changed.
void RemoteRequest(TorcRPCRequest *Request)
Initiate a Remote Procedure Call.
void Start(void)
Initialise the websocket once its parent thread is ready.
#define TORC_NAME
Definition: torcupnp.h:16
void AddParameter(const QString &Name, const QVariant &Value)
Add Parameter and value to list of call parameters.
void SetID(int ID)
Set the unique ID for this request.
void ConnectionEstablished(void)
#define TORC_JSON_RPC
void Reset(void)
Reset the read state.
void Respond(QTcpSocket *Socket)