MythTV  master
httprequest.cpp
Go to the documentation of this file.
1 // Program Name: httprequest.cpp
3 // Created : Oct. 21, 2005
4 //
5 // Purpose : Http Request/Response
6 //
7 // Copyright (c) 2005 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see COPYING for details
10 //
12 
13 #include "httprequest.h"
14 
15 #include <QFile>
16 #include <QFileInfo>
17 #include <QHostInfo>
18 #include <QTextCodec>
19 #include <QStringList>
20 #include <QCryptographicHash>
21 #include <QDateTime>
22 #include <Qt>
23 
24 #include "mythconfig.h"
25 #if !( CONFIG_DARWIN || CONFIG_CYGWIN || defined(__FreeBSD__) || defined(_WIN32))
26 #define USE_SETSOCKOPT
27 #include <sys/sendfile.h>
28 #endif
29 #include <cerrno>
30 #include <cstdlib>
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 // FOR DEBUGGING
35 #include <iostream>
36 
37 #ifndef _WIN32
38 #include <netinet/tcp.h>
39 #endif
40 
41 #include "upnp.h"
42 
43 #include "compat.h"
44 #include "mythlogging.h"
45 #include "mythversion.h"
46 #include "mythdate.h"
47 #include "mythcorecontext.h"
48 #include "mythtimer.h"
49 #include "mythcoreutil.h"
50 
55 
56 #include <unistd.h> // for gethostname
57 
58 #ifndef O_LARGEFILE
59 #define O_LARGEFILE 0
60 #endif
61 
62 static std::array<const MIMETypes,63> g_MIMETypes
63 {{
64  // Image Mime Types
65  { "gif" , "image/gif" },
66  { "ico" , "image/x-icon" },
67  { "jpeg", "image/jpeg" },
68  { "jpg" , "image/jpeg" },
69  { "mng" , "image/x-mng" },
70  { "png" , "image/png" },
71  { "svg" , "image/svg+xml" },
72  { "svgz", "image/svg+xml" },
73  { "tif" , "image/tiff" },
74  { "tiff", "image/tiff" },
75  // Text Mime Types
76  { "htm" , "text/html" },
77  { "html", "text/html" },
78  { "qsp" , "text/html" },
79  { "txt" , "text/plain" },
80  { "xml" , "text/xml" },
81  { "qxml", "text/xml" },
82  { "xslt", "text/xml" },
83  { "css" , "text/css" },
84  // Application Mime Types
85  { "crt" , "application/x-x509-ca-cert" },
86  { "doc" , "application/vnd.ms-word" },
87  { "gz" , "application/x-tar" },
88  { "js" , "application/javascript" },
89  { "m3u" , "application/x-mpegurl" }, // HTTP Live Streaming
90  { "m3u8", "application/x-mpegurl" }, // HTTP Live Streaming
91  { "ogx" , "application/ogg" }, // http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
92  { "pdf" , "application/pdf" },
93  { "pem" , "application/x-x509-ca-cert" },
94  { "qjs" , "application/javascript" },
95  { "rm" , "application/vnd.rn-realmedia" },
96  { "swf" , "application/x-shockwave-flash" },
97  { "xls" , "application/vnd.ms-excel" },
98  { "zip" , "application/x-tar" },
99  // Audio Mime Types:
100  { "aac" , "audio/mp4" },
101  { "ac3" , "audio/vnd.dolby.dd-raw" }, // DLNA?
102  { "flac", "audio/x-flac" }, // This may become audio/flac in the future
103  { "m4a" , "audio/x-m4a" },
104  { "mid" , "audio/midi" },
105  { "mka" , "audio/x-matroska" },
106  { "mp3" , "audio/mpeg" },
107  { "oga" , "audio/ogg" }, // Defined: http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
108  { "ogg" , "audio/ogg" }, // Defined: http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
109  { "wav" , "audio/wav" },
110  { "wma" , "audio/x-ms-wma" },
111  // Video Mime Types
112  { "3gp" , "video/3gpp" }, // Also audio/3gpp
113  { "3g2" , "video/3gpp2" }, // Also audio/3gpp2
114  { "asx" , "video/x-ms-asf" },
115  { "asf" , "video/x-ms-asf" },
116  { "avi" , "video/x-msvideo" }, // Also video/avi
117  { "m2p" , "video/mp2p" }, // RFC 3555
118  { "m4v" , "video/mp4" },
119  { "mpeg", "video/mp2p" }, // RFC 3555
120  { "mpeg2","video/mp2p" }, // RFC 3555
121  { "mpg" , "video/mp2p" }, // RFC 3555
122  { "mpg2", "video/mp2p" }, // RFC 3555
123  { "mov" , "video/quicktime" },
124  { "mp4" , "video/mp4" },
125  { "mkv" , "video/x-matroska" }, // See http://matroska.org/technical/specs/notes.html#MIME (See NOTE 1)
126  { "nuv" , "video/nupplevideo" },
127  { "ogv" , "video/ogg" }, // Defined: http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
128  { "ps" , "video/mp2p" }, // RFC 3555
129  { "ts" , "video/mp2t" }, // RFC 3555
130  { "vob" , "video/mpeg" }, // Also video/dvd
131  { "wmv" , "video/x-ms-wmv" }
132 }};
133 
134 // NOTE 1
135 // This formerly was video/x-matroska, but got changed due to #8643
136 // This was reverted from video/x-mkv, due to #10980
137 // See http://matroska.org/technical/specs/notes.html#MIME
138 // If you can't please everyone, may as well be correct as you piss some off
139 
140 static QString StaticPage =
141  "<!DOCTYPE html>"
142  "<HTML>"
143  "<HEAD>"
144  "<TITLE>Error %1</TITLE>"
145  "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
146  "</HEAD>"
147  "<BODY><H1>%2.</H1></BODY>"
148  "</HTML>";
149 
150 #ifdef USE_SETSOCKOPT
151 //static const int g_on = 1;
152 //static const int g_off = 0;
153 #endif
154 
155 const char *HTTPRequest::s_szServerHeaders = "Accept-Ranges: bytes\r\n";
156 
158 //
160 
161 QString HTTPRequest::GetLastHeader( const QString &sType ) const
162 {
163  QStringList values = m_mapHeaders.values( sType );
164  if (!values.isEmpty())
165  return values.last();
166  return QString();
167 }
168 
170 //
172 
174 {
175  // HTTP
176  if (sType == "GET" ) return( m_eType = RequestTypeGet );
177  if (sType == "HEAD" ) return( m_eType = RequestTypeHead );
178  if (sType == "POST" ) return( m_eType = RequestTypePost );
179  if (sType == "OPTIONS" ) return( m_eType = RequestTypeOptions );
180 
181  // UPnP
182  if (sType == "M-SEARCH" ) return( m_eType = RequestTypeMSearch );
183  if (sType == "NOTIFY" ) return( m_eType = RequestTypeNotify );
184  if (sType == "SUBSCRIBE" ) return( m_eType = RequestTypeSubscribe );
185  if (sType == "UNSUBSCRIBE") return( m_eType = RequestTypeUnsubscribe );
186 
187  if (sType.startsWith( QString("HTTP/") )) return( m_eType = RequestTypeResponse );
188 
189  LOG(VB_HTTP, LOG_INFO,
190  QString("HTTPRequest::SentRequestType( %1 ) - returning Unknown.")
191  .arg(sType));
192 
193  return( m_eType = RequestTypeUnknown);
194 }
195 
197 //
199 
200 QString HTTPRequest::BuildResponseHeader( long long nSize )
201 {
202  QString sHeader;
203  QString sContentType = (m_eResponseType == ResponseTypeOther) ?
205  //-----------------------------------------------------------------------
206  // Headers describing the connection
207  //-----------------------------------------------------------------------
208 
209  // The protocol string
210  sHeader = QString( "%1 %2\r\n" ).arg(GetResponseProtocol())
211  .arg(GetResponseStatus());
212 
215 
216  SetResponseHeader("Connection", m_bKeepAlive ? "Keep-Alive" : "Close" );
217  if (m_bKeepAlive)
218  {
219  if (m_nKeepAliveTimeout == 0) // Value wasn't passed in by the server, so go with the configured value
220  m_nKeepAliveTimeout = gCoreContext->GetNumSetting("HTTP/KeepAliveTimeoutSecs", 10);
221  SetResponseHeader("Keep-Alive", QString("timeout=%1").arg(m_nKeepAliveTimeout));
222  }
223 
224  //-----------------------------------------------------------------------
225  // Entity Headers - Describe the content and allowed methods
226  // RFC 2616 Section 7.1
227  //-----------------------------------------------------------------------
228  if (m_eResponseType != ResponseTypeHeader) // No entity headers
229  {
230  SetResponseHeader("Content-Language", gCoreContext->GetLanguageAndVariant().replace("_", "-"));
231  SetResponseHeader("Content-Type", sContentType);
232 
233  // Default to 'inline' but we should support 'attachment' when it would
234  // be appropriate i.e. not when streaming a file to a upnp player or browser
235  // that can support it natively
236  if (!m_sFileName.isEmpty())
237  {
238  // TODO: Add support for utf8 encoding - RFC 5987
239  QString filename = QFileInfo(m_sFileName).fileName(); // Strip any path
240  SetResponseHeader("Content-Disposition", QString("inline; filename=\"%2\"").arg(QString(filename.toLatin1())));
241  }
242 
243  SetResponseHeader("Content-Length", QString::number(nSize));
244 
245  // See DLNA 7.4.1.3.11.4.3 Tolerance to unavailable contentFeatures.dlna.org header
246  //
247  // It is better not to return this header, than to return it containing
248  // invalid or incomplete information. We are unable to currently determine
249  // this information at this stage, so do not return it. Only older devices
250  // look for it. Newer devices use the information provided in the UPnP
251  // response
252 
253 // QString sValue = GetHeaderValue( "getContentFeatures.dlna.org", "0" );
254 //
255 // if (sValue == "1")
256 // sHeader += "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0;"
257 // "DLNA.ORG_FLAGS=01500000000000000000000000000000\r\n";
258 
259 
260  // DLNA 7.5.4.3.2.33 MT transfer mode indication
261  QString sTransferMode = GetRequestHeader( "transferMode.dlna.org", "" );
262 
263  if (sTransferMode.isEmpty())
264  {
265  if (m_sResponseTypeText.startsWith("video/") ||
266  m_sResponseTypeText.startsWith("audio/"))
267  sTransferMode = "Streaming";
268  else
269  sTransferMode = "Interactive";
270  }
271 
272  if (sTransferMode == "Streaming")
273  SetResponseHeader("transferMode.dlna.org", "Streaming");
274  else if (sTransferMode == "Background")
275  SetResponseHeader("transferMode.dlna.org", "Background");
276  else if (sTransferMode == "Interactive")
277  SetResponseHeader("transferMode.dlna.org", "Interactive");
278 
279  // HACK Temporary hack for Samsung TVs - Needs to be moved later as it's not entirely DLNA compliant
280  if (!GetRequestHeader( "getcontentFeatures.dlna.org", "" ).isEmpty())
281  SetResponseHeader("contentFeatures.dlna.org", "DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000");
282  }
283 
284  auto values = m_mapHeaders.values("origin");
285  for (const auto & value : values)
286  AddCORSHeaders(value);
287 
288  if (getenv("HTTPREQUEST_DEBUG"))
289  {
290  // Dump response header
291  QMap<QString, QString>::iterator it;
292  for ( it = m_mapRespHeaders.begin(); it != m_mapRespHeaders.end(); ++it )
293  {
294  LOG(VB_HTTP, LOG_INFO, QString("(Response Header) %1: %2").arg(it.key()).arg(it.value()));
295  }
296  }
297 
298  sHeader += GetResponseHeaders();
299  sHeader += "\r\n";
300 
301  return sHeader;
302 }
303 
305 //
307 
309 {
310  qint64 nBytes = 0;
311 
312  switch( m_eResponseType )
313  {
314  // The following are all eligable for gzip compression
315  case ResponseTypeUnknown:
316  case ResponseTypeNone:
317  LOG(VB_HTTP, LOG_INFO,
318  QString("HTTPRequest::SendResponse( None ) :%1 -> %2:")
320  return( -1 );
321  case ResponseTypeJS:
322  case ResponseTypeCSS:
323  case ResponseTypeText:
324  case ResponseTypeSVG:
325  case ResponseTypeXML:
326  case ResponseTypeHTML:
327  // If the reponse isn't already in the buffer, then load it
328  if (m_sFileName.isEmpty() || !m_response.buffer().isEmpty())
329  break;
330  {
331  QFile file(m_sFileName);
332  if (file.exists() && file.size() < (2 * 1024 * 1024) && // For security/stability, limit size of files read into buffer to 2MiB
333  file.open(QIODevice::ReadOnly | QIODevice::Text))
334  m_response.buffer() = file.readAll();
335 
336  if (!m_response.buffer().isEmpty())
337  break;
338 
339  // Let SendResponseFile try or send a 404
341  }
342  [[clang::fallthrough]];
343  case ResponseTypeFile: // Binary files
344  LOG(VB_HTTP, LOG_INFO,
345  QString("HTTPRequest::SendResponse( File ) :%1 -> %2:")
347  return( SendResponseFile( m_sFileName ));
348  case ResponseTypeOther:
349  case ResponseTypeHeader:
350  default:
351  break;
352  }
353 
354  LOG(VB_HTTP, LOG_INFO,
355  QString("HTTPRequest::SendResponse(xml/html) (%1) :%2 -> %3: %4")
358 
359  // ----------------------------------------------------------------------
360  // Make it so the header is sent with the data
361  // ----------------------------------------------------------------------
362 
363 #ifdef USE_SETSOCKOPT
364 // // Never send out partially complete segments
365 // if (setsockopt(getSocketHandle(), SOL_TCP, TCP_CORK,
366 // &g_on, sizeof( g_on )) < 0)
367 // {
368 // LOG(VB_HTTP, LOG_INFO,
369 // QString("HTTPRequest::SendResponse(xml/html) "
370 // "setsockopt error setting TCP_CORK on ") + ENO);
371 // }
372 #endif
373 
374 
375 
376  // ----------------------------------------------------------------------
377  // Check for ETag match...
378  // ----------------------------------------------------------------------
379 
380  QString sETag = GetRequestHeader( "If-None-Match", "" );
381 
382  if ( !sETag.isEmpty() && sETag == m_mapRespHeaders[ "ETag" ] )
383  {
384  LOG(VB_HTTP, LOG_INFO,
385  QString("HTTPRequest::SendResponse(%1) - Cached")
386  .arg(sETag));
387 
388  m_nResponseStatus = 304;
389  m_eResponseType = ResponseTypeHeader; // No entity headers
390 
391  // no content can be returned.
392  m_response.buffer().clear();
393  }
394 
395  // ----------------------------------------------------------------------
396 
397  int nContentLen = m_response.buffer().length();
398 
399  QBuffer *pBuffer = &m_response;
400 
401  // ----------------------------------------------------------------------
402  // DEBUGGING
403  if (getenv("HTTPREQUEST_DEBUG"))
404  std::cout << m_response.buffer().constData() << std::endl;
405  // ----------------------------------------------------------------------
406 
407  LOG(VB_HTTP, LOG_DEBUG, QString("Reponse Content Length: %1").arg(nContentLen));
408 
409  // ----------------------------------------------------------------------
410  // Should we try to return data gzip'd?
411  // ----------------------------------------------------------------------
412 
413  QBuffer compBuffer;
414 
415  auto values = m_mapHeaders.values("accept-encoding");
416  bool gzip_found = std::any_of(values.cbegin(), values.cend(),
417  [](const auto & value)
418  {return value.contains( "gzip" ); });
419 
420  if (( nContentLen > 0 ) && gzip_found)
421  {
422  QByteArray compressed = gzipCompress( m_response.buffer() );
423  compBuffer.setData( compressed );
424 
425  if (!compBuffer.buffer().isEmpty())
426  {
427  pBuffer = &compBuffer;
428 
429  SetResponseHeader( "Content-Encoding", "gzip" );
430  LOG(VB_HTTP, LOG_DEBUG, QString("Reponse Compressed Content Length: %1").arg(compBuffer.buffer().length()));
431  }
432  }
433 
434  // ----------------------------------------------------------------------
435  // Write out Header.
436  // ----------------------------------------------------------------------
437 
438  nContentLen = pBuffer->buffer().length();
439 
440  QString rHeader = BuildResponseHeader( nContentLen );
441 
442  QByteArray sHeader = rHeader.toUtf8();
443  LOG(VB_HTTP, LOG_DEBUG, QString("Response header size: %1 bytes").arg(sHeader.length()));
444  nBytes = WriteBlock( sHeader.constData(), sHeader.length() );
445 
446  if (nBytes < sHeader.length())
447  {
448  LOG( VB_HTTP, LOG_ERR, QString("HttpRequest::SendResponse(): "
449  "Incomplete write of header, "
450  "%1 written of %2")
451  .arg(nBytes).arg(sHeader.length()));
452  }
453 
454  // ----------------------------------------------------------------------
455  // Write out Response buffer.
456  // ----------------------------------------------------------------------
457 
458  if (( m_eType != RequestTypeHead ) &&
459  ( nContentLen > 0 ))
460  {
461  qint64 bytesWritten = SendData( pBuffer, 0, nContentLen );
462  //qint64 bytesWritten = WriteBlock( pBuffer->buffer(), pBuffer->buffer().length() );
463 
464  if (bytesWritten != nContentLen)
465  LOG(VB_HTTP, LOG_ERR, "HttpRequest::SendResponse(): Error occurred while writing response body.");
466  else
467  nBytes += bytesWritten;
468  }
469 
470  // ----------------------------------------------------------------------
471  // Turn off the option so any small remaining packets will be sent
472  // ----------------------------------------------------------------------
473 
474 #ifdef USE_SETSOCKOPT
475 // if (setsockopt(getSocketHandle(), SOL_TCP, TCP_CORK,
476 // &g_off, sizeof( g_off )) < 0)
477 // {
478 // LOG(VB_HTTP, LOG_INFO,
479 // QString("HTTPRequest::SendResponse(xml/html) "
480 // "setsockopt error setting TCP_CORK off ") + ENO);
481 // }
482 #endif
483 
484  return( nBytes );
485 }
486 
488 //
490 
491 qint64 HTTPRequest::SendResponseFile( const QString& sFileName )
492 {
493  qint64 nBytes = 0;
494  long long llSize = 0;
495  long long llStart = 0;
496  long long llEnd = 0;
497 
498  LOG(VB_HTTP, LOG_INFO, QString("SendResponseFile ( %1 )").arg(sFileName));
499 
501  m_sResponseTypeText = "text/plain";
502 
503  // ----------------------------------------------------------------------
504  // Make it so the header is sent with the data
505  // ----------------------------------------------------------------------
506 
507 #ifdef USE_SETSOCKOPT
508 // // Never send out partially complete segments
509 // if (setsockopt(getSocketHandle(), SOL_TCP, TCP_CORK,
510 // &g_on, sizeof( g_on )) < 0)
511 // {
512 // LOG(VB_HTTP, LOG_INFO,
513 // QString("HTTPRequest::SendResponseFile(%1) "
514 // "setsockopt error setting TCP_CORK on " ).arg(sFileName) +
515 // ENO);
516 // }
517 #endif
518 
519  QFile tmpFile( sFileName );
520  if (tmpFile.exists( ) && tmpFile.open( QIODevice::ReadOnly ))
521  {
522 
523  m_sResponseTypeText = TestMimeType( sFileName );
524 
525  // ------------------------------------------------------------------
526  // Get File size
527  // ------------------------------------------------------------------
528 
529  llSize = llEnd = tmpFile.size( );
530 
531  m_nResponseStatus = 200;
532 
533  // ------------------------------------------------------------------
534  // Process any Range Header
535  // ------------------------------------------------------------------
536 
537  bool bRange = false;
538  QString sRange = GetRequestHeader( "range", "" );
539 
540  if (!sRange.isEmpty())
541  {
542  bRange = ParseRange( sRange, llSize, &llStart, &llEnd );
543 
544  // Adjust ranges that are too long.
545 
546  if (llEnd >= llSize)
547  llEnd = llSize-1;
548 
549  if ((llSize > llStart) && (llSize > llEnd) && (llEnd > llStart))
550  {
551  if (bRange)
552  {
553  m_nResponseStatus = 206;
554  m_mapRespHeaders[ "Content-Range" ] = QString("bytes %1-%2/%3")
555  .arg( llStart )
556  .arg( llEnd )
557  .arg( llSize );
558  llSize = (llEnd - llStart) + 1;
559  }
560  }
561  else
562  {
563  m_nResponseStatus = 416;
564  // RFC 7233 - A server generating a 416 (Range Not Satisfiable)
565  // response to a byte-range request SHOULD send a Content-Range
566  // header field with an unsatisfied-range value
567  m_mapRespHeaders[ "Content-Range" ] = QString("bytes */%3")
568  .arg( llSize );
569  llSize = 0;
570  LOG(VB_HTTP, LOG_INFO,
571  QString("HTTPRequest::SendResponseFile(%1) - "
572  "invalid byte range %2-%3/%4")
573  .arg(sFileName) .arg(llStart) .arg(llEnd)
574  .arg(llSize));
575  }
576  }
577 
578  // HACK: D-Link DSM-320
579  // The following headers are only required by servers which don't support
580  // http keep alive. We do support it, so we don't need it. Keeping it in
581  // place to prevent someone re-adding it in future
582  //m_mapRespHeaders[ "X-User-Agent" ] = "redsonic";
583 
584  // ------------------------------------------------------------------
585  //
586  // ------------------------------------------------------------------
587 
588  }
589  else
590  {
591  LOG(VB_HTTP, LOG_INFO,
592  QString("HTTPRequest::SendResponseFile(%1) - cannot find file!")
593  .arg(sFileName));
594  m_nResponseStatus = 404;
595  m_response.write( GetResponsePage() );
596  }
597 
598  // -=>TODO: Should set "Content-Length: *" if file is still recording
599 
600  // ----------------------------------------------------------------------
601  // Write out Header.
602  // ----------------------------------------------------------------------
603 
604  QString rHeader = BuildResponseHeader( llSize );
605  QByteArray sHeader = rHeader.toUtf8();
606  LOG(VB_HTTP, LOG_DEBUG, QString("Response header size: %1 bytes").arg(sHeader.length()));
607  nBytes = WriteBlock( sHeader.constData(), sHeader.length() );
608 
609  if (nBytes < sHeader.length())
610  {
611  LOG( VB_HTTP, LOG_ERR, QString("HttpRequest::SendResponseFile(): "
612  "Incomplete write of header, "
613  "%1 written of %2")
614  .arg(nBytes).arg(sHeader.length()));
615  }
616 
617  // ----------------------------------------------------------------------
618  // Write out File.
619  // ----------------------------------------------------------------------
620 
621 #if 0
622  LOG(VB_HTTP, LOG_DEBUG,
623  QString("SendResponseFile : size = %1, start = %2, end = %3")
624  .arg(llSize).arg(llStart).arg(llEnd));
625 #endif
626  if (( m_eType != RequestTypeHead ) && (llSize != 0))
627  {
628  long long sent = SendFile( tmpFile, llStart, llSize );
629 
630  if (sent == -1)
631  {
632  LOG(VB_HTTP, LOG_INFO,
633  QString("SendResponseFile( %1 ) Error: %2 [%3]" )
634  .arg(sFileName) .arg(errno) .arg(strerror(errno)));
635 
636  nBytes = -1;
637  }
638  }
639 
640  // ----------------------------------------------------------------------
641  // Turn off the option so any small remaining packets will be sent
642  // ----------------------------------------------------------------------
643 
644 #ifdef USE_SETSOCKOPT
645 // if (setsockopt(getSocketHandle(), SOL_TCP, TCP_CORK,
646 // &g_off, sizeof( g_off )) < 0)
647 // {
648 // LOG(VB_HTTP, LOG_INFO,
649 // QString("HTTPRequest::SendResponseFile(%1) "
650 // "setsockopt error setting TCP_CORK off ").arg(sFileName) +
651 // ENO);
652 // }
653 #endif
654 
655  // -=>TODO: Only returns header length...
656  // should we change to return total bytes?
657 
658  return nBytes;
659 }
660 
662 //
664 
665 #define SENDFILE_BUFFER_SIZE 65536
666 
667 qint64 HTTPRequest::SendData( QIODevice *pDevice, qint64 llStart, qint64 llBytes )
668 {
669  bool bShouldClose = false;
670  qint64 sent = 0;
671 
672  if (!pDevice->isOpen())
673  {
674  pDevice->open( QIODevice::ReadOnly );
675  bShouldClose = true;
676  }
677 
678  // ----------------------------------------------------------------------
679  // Set out file position to requested start location.
680  // ----------------------------------------------------------------------
681 
682  if ( !pDevice->seek( llStart ))
683  return -1;
684 
685  std::array<char,SENDFILE_BUFFER_SIZE> aBuffer {};
686 
687  qint64 llBytesRemaining = llBytes;
688  qint64 llBytesToRead = 0;
689  qint64 llBytesRead = 0;
690 
691  while ((sent < llBytes) && !pDevice->atEnd())
692  {
693  llBytesToRead = std::min( (qint64)SENDFILE_BUFFER_SIZE, llBytesRemaining );
694 
695  if (( llBytesRead = pDevice->read( aBuffer.data(), llBytesToRead )) != -1 )
696  {
697  if ( WriteBlock( aBuffer.data(), llBytesRead ) == -1)
698  return -1;
699 
700  // -=>TODO: We don't handle the situation where we read more than was sent.
701 
702  sent += llBytesRead;
703  llBytesRemaining -= llBytesRead;
704  }
705  }
706 
707  if (bShouldClose)
708  pDevice->close();
709 
710  return sent;
711 }
712 
714 //
716 
717 qint64 HTTPRequest::SendFile( QFile &file, qint64 llStart, qint64 llBytes )
718 {
719  qint64 sent = SendData( (QIODevice *)(&file), llStart, llBytes );
720 
721  return( sent );
722 }
723 
724 
726 //
728 
729 void HTTPRequest::FormatErrorResponse( bool bServerError,
730  const QString &sFaultString,
731  const QString &sDetails )
732 {
734  m_nResponseStatus = 500;
735 
736  QTextStream stream( &m_response );
737 
738  stream << R"(<?xml version="1.0" encoding="utf-8"?>)";
739 
740  QString sWhere = ( bServerError ) ? "s:Server" : "s:Client";
741 
742  if (m_bSOAPRequest)
743  {
744  m_mapRespHeaders[ "EXT" ] = "";
745 
746  stream << SOAP_ENVELOPE_BEGIN
747  << "<s:Fault>"
748  << "<faultcode>" << sWhere << "</faultcode>"
749  << "<faultstring>" << sFaultString << "</faultstring>";
750  }
751 
752  if (!sDetails.isEmpty())
753  {
754  stream << "<detail>" << sDetails << "</detail>";
755  }
756 
757  if (m_bSOAPRequest)
758  stream << "</s:Fault>" << SOAP_ENVELOPE_END;
759 
760  stream.flush();
761 }
762 
764 //
766 
768 {
771  m_nResponseStatus = 200;
772 
773  pSer->AddHeaders( m_mapRespHeaders );
774 
775  //m_response << pFormatter->ToString();
776 }
777 
779 //
781 
783 {
785  m_nResponseStatus = 200;
786 
787  QTextStream stream( &m_response );
788 
789  stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
790 
791  if (m_bSOAPRequest)
792  {
793  m_mapRespHeaders[ "EXT" ] = "";
794 
795  stream << SOAP_ENVELOPE_BEGIN
796  << "<u:" << m_sMethod << "Response xmlns:u=\""
797  << m_sNameSpace << "\">\r\n";
798  }
799  else
800  stream << "<" << m_sMethod << "Response>\r\n";
801 
802  for (const auto & arg : qAsConst(args))
803  {
804  stream << "<" << arg.m_sName;
805 
806  if (arg.m_pAttributes)
807  {
808  for (const auto & attr : qAsConst(*arg.m_pAttributes))
809  {
810  stream << " " << attr.m_sName << "='"
811  << Encode( attr.m_sValue ) << "'";
812  }
813  }
814 
815  stream << ">";
816 
817  if (m_bSOAPRequest)
818  stream << Encode( arg.m_sValue );
819  else
820  stream << arg.m_sValue;
821 
822  stream << "</" << arg.m_sName << ">\r\n";
823  }
824 
825  if (m_bSOAPRequest)
826  {
827  stream << "</u:" << m_sMethod << "Response>\r\n"
829  }
830  else
831  stream << "</" << m_sMethod << "Response>\r\n";
832 
833  stream.flush();
834 }
835 
837 //
839 
840 void HTTPRequest::FormatRawResponse(const QString &sXML)
841 {
843  m_nResponseStatus = 200;
844 
845  QTextStream stream( &m_response );
846 
847  stream << sXML;
848 
849  stream.flush();
850 }
852 //
854 
855 void HTTPRequest::FormatFileResponse( const QString &sFileName )
856 {
857  m_sFileName = sFileName;
858  QFileInfo file(m_sFileName);
859 
860  if (!m_sFileName.isEmpty() && file.exists())
861  {
862  QDateTime ims = QDateTime::fromString(GetRequestHeader("if-modified-since", ""), Qt::RFC2822Date);
863  ims.setTimeSpec(Qt::OffsetFromUTC);
864  if (ims.isValid() && ims <= file.lastModified()) // Strong validator
865  {
867  m_nResponseStatus = 304; // Not Modified
868  }
869  else
870  {
873  m_nResponseStatus = 200; // OK
874  SetResponseHeader("Last-Modified", MythDate::toString(file.lastModified(),
876  MythDate::kRFC822))); // RFC 822
877  SetResponseHeader("Cache-Control", "no-cache=\"Ext\", max-age = 7200"); // 2 Hours
878  }
879  }
880  else
881  {
883  m_nResponseStatus = 404; // Resource not found
884  m_response.write( GetResponsePage() );
885  LOG(VB_HTTP, LOG_INFO,
886  QString("HTTPRequest::FormatFileResponse('%1') - cannot find file")
887  .arg(sFileName));
888  }
889 }
890 
892 //
894 
895 void HTTPRequest::SetRequestProtocol( const QString &sLine )
896 {
897  m_sProtocol = sLine.section( '/', 0, 0 ).trimmed();
898  QString sVersion = sLine.section( '/', 1 ).trimmed();
899 
900  m_nMajor = sVersion.section( '.', 0, 0 ).toInt();
901  m_nMinor = sVersion.section( '.', 1 ).toInt();
902 }
903 
905 //
907 
909 {
910  return QString("%1/%2.%3").arg(m_sProtocol)
911  .arg(QString::number(m_nMajor))
912  .arg(QString::number(m_nMinor));
913 }
914 
916 //
918 
920 {
921  // RFC 2145
922  //
923  // An HTTP server SHOULD send a response version equal to the highest
924  // version for which the server is at least conditionally compliant, and
925  // whose major version is less than or equal to the one received in the
926  // request.
927 
928 // if (m_nMajor == 1)
929 // QString("HTTP/1.1");
930 // else if (m_nMajor == 2)
931 // QString("HTTP/2.0");
932 
933  return QString("HTTP/1.1");
934 }
935 
937 //
939 
941 {
942  if ((sType == "application/x-www-form-urlencoded" ) ||
943  (sType.startsWith("application/x-www-form-urlencoded;")))
945 
946  if ((sType == "text/xml" ) ||
947  (sType.startsWith("text/xml;") ))
948  return( m_eContentType = ContentType_XML );
949 
951 }
952 
953 
955 //
957 
958 QString HTTPRequest::GetResponseStatus( void ) const
959 {
960  switch( m_nResponseStatus )
961  {
962  case 200: return( "200 OK" );
963  case 201: return( "201 Created" );
964  case 202: return( "202 Accepted" );
965  case 204: return( "204 No Content" );
966  case 205: return( "205 Reset Content" );
967  case 206: return( "206 Partial Content" );
968  case 300: return( "300 Multiple Choices" );
969  case 301: return( "301 Moved Permanently" );
970  case 302: return( "302 Found" );
971  case 303: return( "303 See Other" );
972  case 304: return( "304 Not Modified" );
973  case 305: return( "305 Use Proxy" );
974  case 307: return( "307 Temporary Redirect" );
975  case 308: return( "308 Permanent Redirect" );
976  case 400: return( "400 Bad Request" );
977  case 401: return( "401 Unauthorized" );
978  case 403: return( "403 Forbidden" );
979  case 404: return( "404 Not Found" );
980  case 405: return( "405 Method Not Allowed" );
981  case 406: return( "406 Not Acceptable" );
982  case 408: return( "408 Request Timeout" );
983  case 410: return( "410 Gone" );
984  case 411: return( "411 Length Required" );
985  case 412: return( "412 Precondition Failed" );
986  case 413: return( "413 Request Entity Too Large" );
987  case 414: return( "414 Request-URI Too Long" );
988  case 415: return( "415 Unsupported Media Type" );
989  case 416: return( "416 Requested Range Not Satisfiable" );
990  case 417: return( "417 Expectation Failed" );
991  // I'm a teapot
992  case 428: return( "428 Precondition Required" ); // RFC 6585
993  case 429: return( "429 Too Many Requests" ); // RFC 6585
994  case 431: return( "431 Request Header Fields Too Large" ); // RFC 6585
995  case 500: return( "500 Internal Server Error" );
996  case 501: return( "501 Not Implemented" );
997  case 502: return( "502 Bad Gateway" );
998  case 503: return( "503 Service Unavailable" );
999  case 504: return( "504 Gateway Timeout" );
1000  case 505: return( "505 HTTP Version Not Supported" );
1001  case 510: return( "510 Not Extended" );
1002  case 511: return( "511 Network Authentication Required" ); // RFC 6585
1003  }
1004 
1005  return( QString( "%1 Unknown" ).arg( m_nResponseStatus ));
1006 }
1007 
1009 //
1011 
1013 {
1014  return StaticPage.arg(QString::number(m_nResponseStatus)).arg(GetResponseStatus()).toUtf8();
1015 }
1016 
1018 //
1020 
1021 QString HTTPRequest::GetResponseType( void ) const
1022 {
1023  switch( m_eResponseType )
1024  {
1025  case ResponseTypeXML : return( "text/xml; charset=\"UTF-8\"" );
1026  case ResponseTypeHTML : return( "text/html; charset=\"UTF-8\"" );
1027  case ResponseTypeCSS : return( "text/css; charset=\"UTF-8\"" );
1028  case ResponseTypeJS : return( "application/javascript" );
1029  case ResponseTypeText : return( "text/plain; charset=\"UTF-8\"" );
1030  case ResponseTypeSVG : return( "image/svg+xml" );
1031  default: break;
1032  }
1033 
1034  return( "text/plain" );
1035 }
1036 
1038 //
1040 
1041 QString HTTPRequest::GetMimeType( const QString &sFileExtension )
1042 {
1043  QString ext;
1044 
1045  for (const auto & type : g_MIMETypes)
1046  {
1047  ext = type.pszExtension;
1048 
1049  if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
1050  return( type.pszType );
1051  }
1052 
1053  return( "text/plain" );
1054 }
1055 
1057 //
1059 
1061 {
1062  QStringList mimeTypes;
1063 
1064  for (const auto & type : g_MIMETypes)
1065  {
1066  if (!mimeTypes.contains( type.pszType ))
1067  mimeTypes.append( type.pszType );
1068  }
1069 
1070  return mimeTypes;
1071 }
1072 
1074 //
1076 
1077 QString HTTPRequest::TestMimeType( const QString &sFileName )
1078 {
1079  QFileInfo info( sFileName );
1080  QString sLOC = "HTTPRequest::TestMimeType(" + sFileName + ") - ";
1081  QString sSuffix = info.suffix().toLower();
1082  QString sMIME = GetMimeType( sSuffix );
1083 
1084  if ( sSuffix == "nuv" ) // If a very old recording, might be an MPEG?
1085  {
1086  // Read the header to find out:
1087  QFile file( sFileName );
1088 
1089  if ( file.open(QIODevice::ReadOnly | QIODevice::Text) )
1090  {
1091  QByteArray head = file.read(8);
1092  QString sHex = head.toHex();
1093 
1094  LOG(VB_HTTP, LOG_DEBUG, sLOC + "file starts with " + sHex);
1095 
1096  if ( sHex == "000001ba44000400" ) // MPEG2 PS
1097  sMIME = "video/mp2p";
1098 
1099  if ( head == "MythTVVi" )
1100  {
1101  file.seek(100);
1102  head = file.read(4);
1103 
1104  if ( head == "DIVX" )
1105  {
1106  LOG(VB_HTTP, LOG_DEBUG, sLOC + "('MythTVVi...DIVXLAME')");
1107  sMIME = "video/mp4";
1108  }
1109  // NuppelVideo is "RJPG" at byte 612
1110  // We could also check the audio (LAME or RAWA),
1111  // but since most UPnP clients choke on Nuppel, no need
1112  }
1113 
1114  file.close();
1115  }
1116  else
1117  LOG(VB_GENERAL, LOG_ERR, sLOC + "Could not read file");
1118  }
1119 
1120  LOG(VB_HTTP, LOG_INFO, sLOC + "type is " + sMIME);
1121  return sMIME;
1122 }
1123 
1125 //
1127 
1128 long HTTPRequest::GetParameters( QString sParams, QStringMap &mapParams )
1129 {
1130  long nCount = 0;
1131 
1132  LOG(VB_HTTP, LOG_INFO, QString("sParams: '%1'").arg(sParams));
1133 
1134  // This looks odd, but it is here to cope with stupid UPnP clients that
1135  // forget to de-escape the URLs. We can't map %26 here as well, as that
1136  // breaks anything that is trying to pass & as part of a name or value.
1137  sParams.replace( "&amp;", "&" );
1138 
1139  if (!sParams.isEmpty())
1140  {
1141 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1142  QStringList params = sParams.split('&', QString::SkipEmptyParts);
1143 #else
1144  QStringList params = sParams.split('&', Qt::SkipEmptyParts);
1145 #endif
1146 
1147  for (const auto & param : qAsConst(params))
1148  {
1149  QString sName = param.section( '=', 0, 0 );
1150  QString sValue = param.section( '=', 1 );
1151  sValue.replace("+"," ");
1152 
1153  if (!sName.isEmpty())
1154  {
1155  sName = QUrl::fromPercentEncoding(sName.toUtf8());
1156  sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1157 
1158  mapParams.insert( sName.trimmed(), sValue );
1159  nCount++;
1160  }
1161  }
1162  }
1163 
1164  return nCount;
1165 }
1166 
1167 
1169 //
1171 
1172 QString HTTPRequest::GetRequestHeader( const QString &sKey, QString sDefault )
1173 {
1174  QStringMap::iterator it = m_mapHeaders.find( sKey.toLower() );
1175 
1176  if ( it == m_mapHeaders.end())
1177  return( sDefault );
1178 
1179  return *it;
1180 }
1181 
1182 
1184 //
1186 
1188 {
1189  QString sHeader = s_szServerHeaders;
1190 
1191  for ( QStringMap::iterator it = m_mapRespHeaders.begin();
1192  it != m_mapRespHeaders.end();
1193  ++it )
1194  {
1195  sHeader += it.key() + ": ";
1196  sHeader += *it + "\r\n";
1197  }
1198 
1199  return( sHeader );
1200 }
1201 
1203 //
1205 
1207 {
1208  // TODO: Think about whether we should use a longer timeout if the client
1209  // has explicitly specified 'Keep-alive'
1210 
1211  // HTTP 1.1 ... server may assume keep-alive
1212  bool bKeepAlive = true;
1213 
1214  // if HTTP/1.0... must default to false
1215  if ((m_nMajor == 1) && (m_nMinor == 0))
1216  bKeepAlive = false;
1217 
1218  // Read Connection Header to see whether the client has explicitly
1219  // asked for the connection to be kept alive or closed after the response
1220  // is sent
1221  QString sConnection = GetRequestHeader( "connection", "default" ).toLower();
1222 
1223  QStringList sValueList = sConnection.split(",");
1224 
1225  if ( sValueList.contains("close") )
1226  {
1227  LOG(VB_HTTP, LOG_DEBUG, "Client requested the connection be closed");
1228  bKeepAlive = false;
1229  }
1230  else if (sValueList.contains("keep-alive"))
1231  bKeepAlive = true;
1232 
1233  return bKeepAlive;
1234 }
1235 
1237 //
1239 
1241 {
1242  QStringList sCookieList = m_mapHeaders.values("cookie");
1243 
1244  QStringList::iterator it;
1245  for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1246  {
1247  QString key = (*it).section('=', 0, 0);
1248  QString value = (*it).section('=', 1);
1249 
1250  m_mapCookies.insert(key, value);
1251  }
1252 }
1253 
1255 //
1257 
1259 {
1260  bool bSuccess = false;
1261 
1262  try
1263  {
1264  // Read first line to determine requestType
1265  QString sRequestLine = ReadLine( 2000 );
1266 
1267  if ( sRequestLine.isEmpty() )
1268  {
1269  LOG(VB_GENERAL, LOG_ERR, "Timeout reading first line of request." );
1270  return false;
1271  }
1272 
1273  // -=>TODO: Should read lines until a valid request???
1274  ProcessRequestLine( sRequestLine );
1275 
1276  if (m_nMajor > 1 || m_nMajor < 0)
1277  {
1279  m_nResponseStatus = 505;
1280  m_response.write( GetResponsePage() );
1281 
1282  return true;
1283  }
1284 
1285  if (m_eType == RequestTypeUnknown)
1286  {
1288  m_nResponseStatus = 501; // Not Implemented
1289  // Conservative list, we can't really know what methods we
1290  // actually allow for an arbitrary resource without some sort of
1291  // high maintenance database
1292  SetResponseHeader("Allow", "GET, HEAD");
1293  m_response.write( GetResponsePage() );
1294  return true;
1295  }
1296 
1297  // Read Header
1298  bool bDone = false;
1299  QString sLine = ReadLine( 2000 );
1300 
1301  while (( !sLine.isEmpty() ) && !bDone )
1302  {
1303  if (sLine != "\r\n")
1304  {
1305  QString sName = sLine.section( ':', 0, 0 ).trimmed();
1306  QString sValue = sLine.section( ':', 1 );
1307 
1308  sValue.truncate( sValue.length() - 2 );
1309 
1310  if (!sName.isEmpty() && !sValue.isEmpty())
1311  {
1312  m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1313  }
1314 
1315  sLine = ReadLine( 2000 );
1316  }
1317  else
1318  bDone = true;
1319  }
1320 
1321  // Dump request header
1322  for ( QStringMap::iterator it = m_mapHeaders.begin();
1323  it != m_mapHeaders.end();
1324  ++it )
1325  {
1326  LOG(VB_HTTP, LOG_INFO, QString("(Request Header) %1: %2")
1327  .arg(it.key()).arg(*it));
1328  }
1329 
1330  // Parse Cookies
1331  ParseCookies();
1332 
1333  // Parse out keep alive
1335 
1336  // Check to see if we found the end of the header or we timed out.
1337  if (!bDone)
1338  {
1339  LOG(VB_GENERAL, LOG_INFO, "Timeout waiting for request header." );
1340  return false;
1341  }
1342 
1343  // HTTP/1.1 requires that the Host header be present, even if empty
1344  if ((m_nMinor == 1) && !m_mapHeaders.contains("host"))
1345  {
1347  m_nResponseStatus = 400;
1348  m_response.write( GetResponsePage() );
1349 
1350  return true;
1351  }
1352 
1353  // Destroy session if requested
1354  if (m_mapHeaders.contains("x-myth-clear-session"))
1355  {
1356  SetCookie("sessionToken", "", MythDate::current().addDays(-2), true);
1357  m_mapCookies.remove("sessionToken");
1358  }
1359 
1360  // Allow session resumption for TLS connections
1361  if (m_mapCookies.contains("sessionToken"))
1362  {
1363  QString sessionToken = m_mapCookies["sessionToken"];
1364  MythSessionManager *sessionManager = gCoreContext->GetSessionManager();
1365  MythUserSession session = sessionManager->GetSession(sessionToken);
1366 
1367  if (session.IsValid())
1368  m_userSession = session;
1369  }
1370 
1371  if (IsUrlProtected( m_sBaseUrl ))
1372  {
1373  if (!Authenticated())
1374  {
1376  m_nResponseStatus = 401;
1377  m_response.write( GetResponsePage() );
1378  // Since this may not be the first attempt at authentication,
1379  // Authenticated may have set the header with the appropriate
1380  // stale attribute
1381  SetResponseHeader("WWW-Authenticate", GetAuthenticationHeader(false));
1382 
1383  return true;
1384  }
1385 
1386  m_bProtected = true;
1387  }
1388 
1389  bSuccess = true;
1390 
1391  SetContentType( GetLastHeader( "content-type" ) );
1392  // Lets load payload if any.
1393  long nPayloadSize = GetLastHeader( "content-length" ).toLong();
1394 
1395  if (nPayloadSize > 0)
1396  {
1397  char *pszPayload = new char[ nPayloadSize + 2 ];
1398  long nBytes = 0;
1399 
1400  nBytes = ReadBlock( pszPayload, nPayloadSize, 5000 );
1401  if (nBytes == nPayloadSize )
1402  {
1403  m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1404 
1405  // See if the payload is just data from a form post
1408  }
1409  else
1410  {
1411  LOG(VB_GENERAL, LOG_ERR,
1412  QString("Unable to read entire payload (read %1 of %2 bytes)")
1413  .arg( nBytes ) .arg( nPayloadSize ) );
1414  bSuccess = false;
1415  }
1416 
1417  delete [] pszPayload;
1418  }
1419 
1420  // Check to see if this is a SOAP encoded message
1421  QString sSOAPAction = GetRequestHeader( "SOAPACTION", "" );
1422 
1423  if (!sSOAPAction.isEmpty())
1424  bSuccess = ProcessSOAPPayload( sSOAPAction );
1425  else
1427 
1428 #if 0
1429  if (m_sMethod != "*" )
1430  LOG(VB_HTTP, LOG_DEBUG,
1431  QString("HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1432  "Method (%3) - Bytes in Socket Buffer (%4)")
1434  .arg(m_sMethod) .arg(BytesAvailable()));
1435 #endif
1436  }
1437  catch(...)
1438  {
1439  LOG(VB_GENERAL, LOG_WARNING,
1440  "Unexpected exception in HTTPRequest::ParseRequest" );
1441  }
1442 
1443  return bSuccess;
1444 }
1445 
1447 //
1449 
1450 void HTTPRequest::ProcessRequestLine( const QString &sLine )
1451 {
1452  m_sRawRequest = sLine;
1453 
1454 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1455  QStringList tokens = sLine.split(m_procReqLineExp, QString::SkipEmptyParts);
1456 #else
1457  QStringList tokens = sLine.split(m_procReqLineExp, Qt::SkipEmptyParts);
1458 #endif
1459  int nCount = tokens.count();
1460 
1461  // ----------------------------------------------------------------------
1462 
1463  if ( sLine.startsWith( QString("HTTP/") ))
1465  else
1467 
1468  // ----------------------------------------------------------------------
1469  // if this is actually a response, then sLine's format will be:
1470  // HTTP/m.n <response code> <response text>
1471  // otherwise:
1472  // <method> <Resource URI> HTTP/m.n
1473  // ----------------------------------------------------------------------
1474 
1476  {
1477  // ------------------------------------------------------------------
1478  // Process as a request
1479  // ------------------------------------------------------------------
1480 
1481  if (nCount > 0)
1482  SetRequestType( tokens[0].trimmed() );
1483 
1484  if (nCount > 1)
1485  {
1486  m_sOriginalUrl = tokens[1].toUtf8(); // Used by authorization check
1487  m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1488  m_sBaseUrl = m_sRequestUrl.section( '?', 0, 0).trimmed();
1489 
1490  m_sResourceUrl = m_sBaseUrl; // Save complete url without parameters
1491 
1492  // Process any Query String Parameters
1493  QString sQueryStr = tokens[1].section( '?', 1, 1 );
1494 
1495  if (!sQueryStr.isEmpty())
1496  GetParameters( sQueryStr, m_mapParams );
1497  }
1498 
1499  if (nCount > 2)
1500  SetRequestProtocol( tokens[2].trimmed() );
1501  }
1502  else
1503  {
1504  // ------------------------------------------------------------------
1505  // Process as a Response
1506  // ------------------------------------------------------------------
1507  if (nCount > 0)
1508  SetRequestProtocol( tokens[0].trimmed() );
1509 
1510  if (nCount > 1)
1511  m_nResponseStatus = tokens[1].toInt();
1512  }
1513 
1514 
1515 }
1516 
1518 //
1520 
1521 bool HTTPRequest::ParseRange( QString sRange,
1522  long long llSize,
1523  long long *pllStart,
1524  long long *pllEnd )
1525 {
1526  // ----------------------------------------------------------------------
1527  // -=>TODO: Only handle 1 range at this time...
1528  // should make work with full spec.
1529  // ----------------------------------------------------------------------
1530 
1531  if (sRange.isEmpty())
1532  return false;
1533 
1534  // ----------------------------------------------------------------------
1535  // remove any "bytes="
1536  // ----------------------------------------------------------------------
1537  int nIdx = sRange.indexOf(m_parseRangeExp);
1538 
1539  if (nIdx < 0)
1540  return false;
1541 
1542  if (nIdx > 0)
1543  sRange.remove( 0, nIdx );
1544 
1545  // ----------------------------------------------------------------------
1546  // Split multiple ranges
1547  // ----------------------------------------------------------------------
1548 
1549 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1550  QStringList ranges = sRange.split(',', QString::SkipEmptyParts);
1551 #else
1552  QStringList ranges = sRange.split(',', Qt::SkipEmptyParts);
1553 #endif
1554 
1555  if (ranges.count() == 0)
1556  return false;
1557 
1558  // ----------------------------------------------------------------------
1559  // Split first range into its components
1560  // ----------------------------------------------------------------------
1561 
1562  QStringList parts = ranges[0].split('-');
1563 
1564  if (parts.count() != 2)
1565  return false;
1566 
1567  if (parts[0].isEmpty() && parts[1].isEmpty())
1568  return false;
1569 
1570  // ----------------------------------------------------------------------
1571  //
1572  // ----------------------------------------------------------------------
1573 
1574  bool conv_ok = false;
1575  if (parts[0].isEmpty())
1576  {
1577  // ------------------------------------------------------------------
1578  // Does it match "-####"
1579  // ------------------------------------------------------------------
1580 
1581  long long llValue = parts[1].toLongLong(&conv_ok);
1582  if (!conv_ok) return false;
1583 
1584  *pllStart = llSize - llValue;
1585  *pllEnd = llSize - 1;
1586  }
1587  else if (parts[1].isEmpty())
1588  {
1589  // ------------------------------------------------------------------
1590  // Does it match "####-"
1591  // ------------------------------------------------------------------
1592 
1593  *pllStart = parts[0].toLongLong(&conv_ok);
1594 
1595  if (!conv_ok)
1596  return false;
1597 
1598  *pllEnd = llSize - 1;
1599  }
1600  else
1601  {
1602  // ------------------------------------------------------------------
1603  // Must be "####-####"
1604  // ------------------------------------------------------------------
1605 
1606  *pllStart = parts[0].toLongLong(&conv_ok);
1607  if (!conv_ok) return false;
1608  *pllEnd = parts[1].toLongLong(&conv_ok);
1609  if (!conv_ok) return false;
1610 
1611  if (*pllStart > *pllEnd)
1612  return false;
1613  }
1614 
1615  LOG(VB_HTTP, LOG_DEBUG, QString("%1 Range Requested %2 - %3")
1616  .arg(getSocketHandle()) .arg(*pllStart) .arg(*pllEnd));
1617 
1618  return true;
1619 }
1620 
1622 //
1624 
1626 {
1627  // Strip out leading http://192.168.1.1:6544/ -> /
1628  // Should fix #8678
1629  // FIXME what about https?
1630  QRegularExpression re {"^http[s]?://.*?/"};
1631  m_sBaseUrl.replace(re, "/");
1632 
1633 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1634  QStringList sList = m_sBaseUrl.split('/', QString::SkipEmptyParts);
1635 #else
1636  QStringList sList = m_sBaseUrl.split('/', Qt::SkipEmptyParts);
1637 #endif
1638 
1639  m_sMethod = "";
1640 
1641  if (!sList.isEmpty())
1642  {
1643  m_sMethod = sList.last();
1644  sList.pop_back();
1645  }
1646 
1647  m_sBaseUrl = '/' + sList.join( "/" );
1648  LOG(VB_HTTP, LOG_INFO, QString("ExtractMethodFromURL(end) : %1 : %2")
1650 }
1651 
1653 //
1655 
1656 bool HTTPRequest::ProcessSOAPPayload( const QString &sSOAPAction )
1657 {
1658  bool bSuccess = false;
1659 
1660  // ----------------------------------------------------------------------
1661  // Open Supplied XML uPnp Description file.
1662  // ----------------------------------------------------------------------
1663 
1664  LOG(VB_HTTP, LOG_INFO,
1665  QString("HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
1666  QDomDocument doc ( "request" );
1667 
1668  QString sErrMsg;
1669  int nErrLine = 0;
1670  int nErrCol = 0;
1671 
1672  if (!doc.setContent( m_sPayload, true, &sErrMsg, &nErrLine, &nErrCol ))
1673  {
1674  LOG(VB_GENERAL, LOG_ERR,
1675  QString( "Error parsing request at line: %1 column: %2 : %3" )
1676  .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
1677  return( false );
1678  }
1679 
1680  // --------------------------------------------------------------
1681  // XML Document Loaded... now parse it
1682  // --------------------------------------------------------------
1683 
1684  QString sService;
1685 
1686  if (sSOAPAction.contains( '#' ))
1687  {
1688  m_sNameSpace = sSOAPAction.section( '#', 0, 0).remove( 0, 1);
1689  m_sMethod = sSOAPAction.section( '#', 1 );
1690  m_sMethod.remove( m_sMethod.length()-1, 1 );
1691  }
1692  else
1693  {
1694  if (sSOAPAction.contains( '/' ))
1695  {
1696  int nPos = sSOAPAction.lastIndexOf( '/' );
1697  m_sNameSpace = sSOAPAction.mid(1, nPos);
1698  m_sMethod = sSOAPAction.mid(nPos + 1,
1699  sSOAPAction.length() - nPos - 2);
1700 
1701  nPos = m_sNameSpace.lastIndexOf( '/', -2);
1702  sService = m_sNameSpace.mid(nPos + 1,
1703  m_sNameSpace.length() - nPos - 2);
1704  m_sNameSpace = m_sNameSpace.mid( 0, nPos );
1705  }
1706  else
1707  {
1708  m_sNameSpace.clear();
1709  m_sMethod = sSOAPAction;
1710  m_sMethod.remove( QChar( '\"' ) );
1711  }
1712  }
1713 
1714  QDomNodeList oNodeList = doc.elementsByTagNameNS( m_sNameSpace, m_sMethod );
1715 
1716  if (oNodeList.count() == 0)
1717  {
1718  oNodeList =
1719  doc.elementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/",
1720  "Body");
1721  }
1722 
1723  if (oNodeList.count() > 0)
1724  {
1725  QDomNode oMethod = oNodeList.item(0);
1726 
1727  if (!oMethod.isNull())
1728  {
1729  m_bSOAPRequest = true;
1730 
1731  for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1732  oNode = oNode.nextSibling() )
1733  {
1734  QDomElement e = oNode.toElement();
1735 
1736  if (!e.isNull())
1737  {
1738  QString sName = e.tagName();
1739  QString sValue = "";
1740 
1741  QDomText oText = oNode.firstChild().toText();
1742 
1743  if (!oText.isNull())
1744  sValue = oText.nodeValue();
1745 
1746  sName = QUrl::fromPercentEncoding(sName.toUtf8());
1747  sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1748 
1749  m_mapParams.insert( sName.trimmed().toLower(), sValue );
1750  }
1751  }
1752 
1753  bSuccess = true;
1754  }
1755  }
1756 
1757  return bSuccess;
1758 }
1759 
1761 //
1763 
1765 {
1766  Serializer *pSerializer = nullptr;
1767 
1768  if (m_bSOAPRequest)
1769  {
1770  pSerializer = (Serializer *)new SoapSerializer(&m_response,
1772  }
1773  else
1774  {
1775  QString sAccept = GetRequestHeader( "Accept", "*/*" );
1776 
1777  if (sAccept.contains( "application/json", Qt::CaseInsensitive ) ||
1778  sAccept.contains( "text/javascript", Qt::CaseInsensitive ))
1779  {
1780  pSerializer = (Serializer *)new JSONSerializer(&m_response,
1781  m_sMethod);
1782  }
1783  else if (sAccept.contains( "text/x-apple-plist+xml", Qt::CaseInsensitive ))
1784  {
1785  pSerializer = (Serializer *)new XmlPListSerializer(&m_response);
1786  }
1787  }
1788 
1789  // Default to XML
1790 
1791  if (pSerializer == nullptr)
1792  pSerializer = (Serializer *)new XmlSerializer(&m_response, m_sMethod);
1793 
1794  return pSerializer;
1795 }
1796 
1798 //
1800 
1801 QString HTTPRequest::Encode(const QString &sIn)
1802 {
1803  QString sStr = sIn;
1804 #if 0
1805  LOG(VB_HTTP, LOG_DEBUG,
1806  QString("HTTPRequest::Encode Input : %1").arg(sStr));
1807 #endif
1808  sStr.replace('&', "&amp;" ); // This _must_ come first
1809  sStr.replace('<', "&lt;" );
1810  sStr.replace('>', "&gt;" );
1811  sStr.replace('"', "&quot;");
1812  sStr.replace("'", "&apos;");
1813 
1814 #if 0
1815  LOG(VB_HTTP, LOG_DEBUG,
1816  QString("HTTPRequest::Encode Output : %1").arg(sStr));
1817 #endif
1818  return sStr;
1819 }
1820 
1822 //
1824 
1825 QString HTTPRequest::Decode(const QString& sIn)
1826 {
1827  QString sStr = sIn;
1828  sStr.replace("&amp;", "&");
1829  sStr.replace("&lt;", "<");
1830  sStr.replace("&gt;", ">");
1831  sStr.replace("&quot;", "\"");
1832  sStr.replace("&apos;", "'");
1833 
1834  return sStr;
1835 }
1836 
1838 //
1840 
1841 QString HTTPRequest::GetETagHash(const QByteArray &data)
1842 {
1843  QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1844 
1845  return ("\"" + hash.toHex() + "\"");
1846 }
1847 
1849 //
1851 
1852 bool HTTPRequest::IsUrlProtected( const QString &sBaseUrl )
1853 {
1854  QString sProtected = UPnp::GetConfiguration()->GetValue( "HTTP/Protected/Urls", "/setup;/Config" );
1855 
1856  QStringList oList = sProtected.split( ';' );
1857 
1858  for( int nIdx = 0; nIdx < oList.count(); nIdx++)
1859  {
1860  if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
1861  return true;
1862  }
1863 
1864  return false;
1865 }
1866 
1868 //
1870 
1872 {
1873  QString authHeader;
1874 
1875  // For now we support a single realm, that will change
1876  QString realm = "MythTV";
1877 
1878  // Always use digest authentication where supported, it may be available
1879  // with HTTP 1.0 client as an extension, but we can't tell if that's the
1880  // case. It's guaranteed to be available for HTTP 1.1+
1881  if (m_nMajor >= 1 && m_nMinor > 0)
1882  {
1884  QString stale = isStale ? "true" : "false"; // FIXME
1885  authHeader = QString("Digest realm=\"%1\",nonce=\"%2\","
1886  "qop=\"auth\",stale=\"%3\",algorithm=\"MD5\"")
1887  .arg(realm).arg(nonce).arg(stale);
1888  }
1889  else
1890  {
1891  authHeader = QString("Basic realm=\"%1\"").arg(realm);
1892  }
1893 
1894  return authHeader;
1895 }
1896 
1898 //
1900 
1901 QString HTTPRequest::CalculateDigestNonce(const QString& timeStamp) const
1902 {
1903  QString uniqueID = QString("%1:%2").arg(timeStamp).arg(m_sPrivateToken);
1904  QString hash = QCryptographicHash::hash( uniqueID.toLatin1(), QCryptographicHash::Sha1).toHex(); // TODO: Change to Sha2 with QT5?
1905  QString nonce = QString("%1%2").arg(timeStamp).arg(hash); // Note: since this is going in a header it should avoid illegal chars
1906  return nonce;
1907 }
1908 
1910 //
1912 
1914 {
1915  LOG(VB_HTTP, LOG_NOTICE, "Attempting HTTP Basic Authentication");
1916  QStringList oList = GetLastHeader( "authorization" ).split( ' ' );
1917 
1918  if (m_nMajor == 1 && m_nMinor == 0) // We only support Basic auth for http 1.0 clients
1919  {
1920  LOG(VB_GENERAL, LOG_WARNING, "Basic authentication is only allowed for HTTP 1.0");
1921  return false;
1922  }
1923 
1924  QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
1925 
1926  oList = sCredentials.split( ':' );
1927 
1928  if (oList.count() < 2)
1929  {
1930  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid number of tokens");
1931  return false;
1932  }
1933 
1934  QString sUsername = oList[0];
1935  QString sPassword = oList[1];
1936 
1937  if (sUsername == "nouser") // Special logout username
1938  return false;
1939 
1940  MythSessionManager *sessionManager = gCoreContext->GetSessionManager();
1941  if (!MythSessionManager::IsValidUser(sUsername))
1942  {
1943  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid username");
1944  return false;
1945  }
1946 
1947  QString client = QString("WebFrontend_%1").arg(GetPeerAddress());
1948  MythUserSession session = sessionManager->LoginUser(sUsername, sPassword,
1949  client);
1950 
1951  if (!session.IsValid())
1952  {
1953  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid password");
1954  return false;
1955  }
1956 
1957  LOG(VB_HTTP, LOG_NOTICE, "Valid Authorization received");
1958 
1959  if (IsEncrypted()) // Only set a session cookie for encrypted connections, not safe otherwise
1960  SetCookie("sessionToken", session.GetSessionToken(),
1961  session.GetSessionExpires(), true);
1962 
1963  m_userSession = session;
1964 
1965  return false;
1966 }
1967 
1969 //
1971 
1973 {
1974  LOG(VB_HTTP, LOG_NOTICE, "Attempting HTTP Digest Authentication");
1975  QString realm = "MythTV"; // TODO Check which realm applies for the request path
1976 
1977  QString authMethod = GetLastHeader( "authorization" ).section(' ', 0, 0).toLower();
1978 
1979  if (authMethod != "digest")
1980  {
1981  LOG(VB_GENERAL, LOG_WARNING, "Invalid method in Authorization header");
1982  return false;
1983  }
1984 
1985  QString parameterStr = GetLastHeader( "authorization" ).section(' ', 1);
1986 
1987  QMap<QString, QString> paramMap;
1988  QStringList paramList = parameterStr.split(',');
1989  QStringList::iterator it;
1990  for (it = paramList.begin(); it != paramList.end(); ++it)
1991  {
1992  QString key = (*it).section('=', 0, 0).toLower().trimmed();
1993  // Since the value may contain '=' return everything after first occurence
1994  QString value = (*it).section('=', 1).trimmed();
1995  // Remove any quotes surrounding the value
1996  value.remove("\"");
1997  paramMap[key] = value;
1998  }
1999 
2000  if (paramMap.size() < 8)
2001  {
2002  LOG(VB_GENERAL, LOG_WARNING, "Invalid number of parameters in Authorization header");
2003  return false;
2004  }
2005 
2006  if (paramMap["nonce"].isEmpty() || paramMap["username"].isEmpty() ||
2007  paramMap["realm"].isEmpty() || paramMap["uri"].isEmpty() ||
2008  paramMap["response"].isEmpty() || paramMap["qop"].isEmpty() ||
2009  paramMap["cnonce"].isEmpty() || paramMap["nc"].isEmpty())
2010  {
2011  LOG(VB_GENERAL, LOG_WARNING, "Missing required parameters in Authorization header");
2012  return false;
2013  }
2014 
2015  if (paramMap["username"] == "nouser") // Special logout username
2016  return false;
2017 
2018  if (paramMap["uri"] != m_sOriginalUrl)
2019  {
2020  LOG(VB_GENERAL, LOG_WARNING, "Authorization URI doesn't match the "
2021  "request URI");
2022  m_nResponseStatus = 400; // Bad Request
2023  return false;
2024  }
2025 
2026  if (paramMap["realm"] != realm)
2027  {
2028  LOG(VB_GENERAL, LOG_WARNING, "Authorization realm doesn't match the "
2029  "realm of the requested content");
2030  return false;
2031  }
2032 
2033  QByteArray nonce = paramMap["nonce"].toLatin1();
2034  if (nonce.length() < 20)
2035  {
2036  LOG(VB_GENERAL, LOG_WARNING, "Authorization nonce is too short");
2037  return false;
2038  }
2039 
2040  QString nonceTimeStampStr = nonce.left(20); // ISO 8601 fixed length
2041  if (nonce != CalculateDigestNonce(nonceTimeStampStr))
2042  {
2043  LOG(VB_GENERAL, LOG_WARNING, "Authorization nonce doesn't match reference");
2044  LOG(VB_HTTP, LOG_DEBUG, QString("%1 vs %2").arg(QString(nonce))
2045  .arg(CalculateDigestNonce(nonceTimeStampStr)));
2046  return false;
2047  }
2048 
2049  const int AUTH_TIMEOUT = 2 * 60; // 2 Minute timeout to login, to reduce replay attack window
2050  QDateTime nonceTimeStamp = MythDate::fromString(nonceTimeStampStr);
2051  if (!nonceTimeStamp.isValid())
2052  {
2053  LOG(VB_GENERAL, LOG_WARNING, "Authorization nonce timestamp is invalid.");
2054  LOG(VB_HTTP, LOG_DEBUG, QString("Timestamp was '%1'").arg(nonceTimeStampStr));
2055  return false;
2056  }
2057 
2058  if (nonceTimeStamp.secsTo(MythDate::current()) > AUTH_TIMEOUT)
2059  {
2060  LOG(VB_HTTP, LOG_NOTICE, "Authorization nonce timestamp is invalid or too old.");
2061  // Tell the client that the submitted nonce has expired at which
2062  // point they may wish to try again with a fresh nonce instead of
2063  // telling the user that their credentials were invalid
2064  SetResponseHeader("WWW-Authenticate", GetAuthenticationHeader(true), true);
2065  return false;
2066  }
2067 
2068  MythSessionManager *sessionManager = gCoreContext->GetSessionManager();
2069  if (!MythSessionManager::IsValidUser(paramMap["username"]))
2070  {
2071  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid username");
2072  return false;
2073  }
2074 
2075  if (paramMap["response"].length() != 32)
2076  {
2077  LOG(VB_GENERAL, LOG_WARNING, "Authorization response field is invalid length");
2078  return false;
2079  }
2080 
2081  // If you're still reading this, well done, not far to go now
2082 
2083  QByteArray a1 = MythSessionManager::GetPasswordDigest(paramMap["username"]).toLatin1();
2084  //QByteArray a1 = "bcd911b2ecb15ffbd6d8e6e744d60cf6";
2085  QString methodDigest = QString("%1:%2").arg(GetRequestType()).arg(paramMap["uri"]);
2086  QByteArray a2 = QCryptographicHash::hash(methodDigest.toLatin1(),
2087  QCryptographicHash::Md5).toHex();
2088 
2089  QString responseDigest = QString("%1:%2:%3:%4:%5:%6").arg(QString(a1))
2090  .arg(paramMap["nonce"])
2091  .arg(paramMap["nc"])
2092  .arg(paramMap["cnonce"])
2093  .arg(paramMap["qop"])
2094  .arg(QString(a2));
2095  QByteArray kd = QCryptographicHash::hash(responseDigest.toLatin1(),
2096  QCryptographicHash::Md5).toHex();
2097 
2098  if (paramMap["response"].toLatin1() == kd)
2099  {
2100  LOG(VB_HTTP, LOG_NOTICE, "Valid Authorization received");
2101  QString client = QString("WebFrontend_%1").arg(GetPeerAddress());
2102  MythUserSession session = sessionManager->LoginUser(paramMap["username"],
2103  a1,
2104  client);
2105  if (!session.IsValid())
2106  {
2107  LOG(VB_GENERAL, LOG_ERR, "Valid Authorization received, but we "
2108  "failed to create a valid session");
2109  return false;
2110  }
2111 
2112  if (IsEncrypted()) // Only set a session cookie for encrypted connections, not safe otherwise
2113  SetCookie("sessionToken", session.GetSessionToken(),
2114  session.GetSessionExpires(), true);
2115 
2116  m_userSession = session;
2117 
2118  return true;
2119  }
2120 
2121  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid password digest");
2122  LOG(VB_HTTP, LOG_DEBUG, QString("Received hash was '%1', calculated hash was '%2'")
2123  .arg(paramMap["response"])
2124  .arg(QString(kd)));
2125 
2126  return false;
2127 }
2128 
2130 //
2132 
2134 {
2135  // Check if the existing user has permission to access this resource
2136  if (m_userSession.IsValid()) //m_userSession.CheckPermission())
2137  return true;
2138 
2139  QStringList oList = GetLastHeader( "authorization" ).split( ' ' );
2140 
2141  if (oList.count() < 2)
2142  return false;
2143 
2144  if (oList[0].compare( "basic", Qt::CaseInsensitive ) == 0)
2145  return BasicAuthentication();
2146  if (oList[0].compare( "digest", Qt::CaseInsensitive ) == 0)
2147  return DigestAuthentication();
2148 
2149  return false;
2150 }
2151 
2153 //
2155 
2156 void HTTPRequest::SetResponseHeader(const QString& sKey, const QString& sValue,
2157  bool replace)
2158 {
2159  if (!replace && m_mapRespHeaders.contains(sKey))
2160  return;
2161 
2162  m_mapRespHeaders[sKey] = sValue;
2163 }
2164 
2166 //
2168 
2169 void HTTPRequest::SetCookie(const QString &sKey, const QString &sValue,
2170  const QDateTime &expiryDate, bool secure)
2171 {
2172  if (secure && !IsEncrypted())
2173  {
2174  LOG(VB_GENERAL, LOG_WARNING, QString("HTTPRequest::SetCookie(%1=%2): "
2175  "A secure cookie cannot be set on an unencrypted connection.")
2176  .arg(sKey).arg(sValue));
2177  return;
2178  }
2179 
2180  QStringList cookieAttributes;
2181 
2182  // Key=Value
2183  cookieAttributes.append(QString("%1=%2").arg(sKey).arg(sValue));
2184 
2185  // Domain - Most browsers have problems with a hostname, so it's better to omit this
2186 // cookieAttributes.append(QString("Domain=%1").arg(GetHostName()));
2187 
2188  // Path - Fix to root, no call for restricting to other paths yet
2189  cookieAttributes.append("Path=/");
2190 
2191  // Expires - Expiry date, always set one, just good practice
2192  QString expires = MythDate::toString(expiryDate, MythDate::kRFC822); // RFC 822
2193  cookieAttributes.append(QString("Expires=%1").arg(expires)); // Cookie Expiry date
2194 
2195  // Secure - Only send this cookie over encrypted connections, it contains
2196  // sensitive info SECURITY
2197  if (secure)
2198  cookieAttributes.append("Secure");
2199 
2200  // HttpOnly - No cookie stealing javascript SECURITY
2201  cookieAttributes.append("HttpOnly");
2202 
2203  SetResponseHeader("Set-Cookie", cookieAttributes.join("; "));
2204 }
2205 
2207 //
2209 
2211 {
2212  // TODO: This only deals with the HTTP 1.1 case, 1.0 should be rare but we
2213  // should probably still handle it
2214 
2215  // RFC 3875 - The is the hostname or ip address in the client request, not
2216  // the name or ip we might otherwise know for this server
2217  QString hostname = GetLastHeader("host");
2218  if (!hostname.isEmpty())
2219  {
2220  // Strip the port
2221  if (hostname.contains("]:")) // IPv6 port
2222  {
2223  return hostname.section("]:", 0 , 0);
2224  }
2225  if (hostname.contains(":")) // IPv4 port
2226  {
2227  return hostname.section(":", 0 , 0);
2228  }
2229  return hostname;
2230  }
2231 
2232  return GetHostAddress();
2233 }
2234 
2235 
2237 {
2238  QString type;
2239  switch ( m_eType )
2240  {
2241  case RequestTypeUnknown :
2242  type = "UNKNOWN";
2243  break;
2244  case RequestTypeGet :
2245  type = "GET";
2246  break;
2247  case RequestTypeHead :
2248  type = "HEAD";
2249  break;
2250  case RequestTypePost :
2251  type = "POST";
2252  break;
2253  case RequestTypeOptions:
2254  type = "OPTIONS";
2255  break;
2256  case RequestTypeMSearch:
2257  type = "M-SEARCH";
2258  break;
2259  case RequestTypeNotify:
2260  type = "NOTIFY";
2261  break;
2262  case RequestTypeSubscribe :
2263  type = "SUBSCRIBE";
2264  break;
2265  case RequestTypeUnsubscribe :
2266  type = "UNSUBSCRIBE";
2267  break;
2268  case RequestTypeResponse :
2269  type = "RESPONSE";
2270  break;
2271  }
2272 
2273  return type;
2274 }
2275 
2276 void HTTPRequest::AddCORSHeaders( const QString &sOrigin )
2277 {
2278  // ----------------------------------------------------------------------
2279  // SECURITY: Access-Control-Allow-Origin Wildcard
2280  //
2281  // This is a REALLY bad idea, so bad in fact that I'm including it here but
2282  // commented out in the hope that anyone thinking of adding it in the future
2283  // will see it and then read this comment.
2284  //
2285  // Browsers do not verify that the origin is on the same network. This means
2286  // that a malicious script embedded or included into ANY webpage you visit
2287  // could then access servers on your local network including MythTV. They
2288  // can grab data, delete data including recordings and videos, schedule
2289  // recordings and generally ruin your day.
2290  //
2291  // This might seem paranoid and a remote possibility, but then that's how
2292  // a lot of exploits are born. Do NOT allow wildcards.
2293  //
2294  //m_mapRespHeaders[ "Access-Control-Allow-Origin" ] = "*";
2295  // ----------------------------------------------------------------------
2296 
2297  // ----------------------------------------------------------------------
2298  // SECURITY: Allow the WebFrontend on the Master backend and ONLY this
2299  // machine to access resources on a frontend or slave web server
2300  //
2301  // http://www.w3.org/TR/cors/#introduction
2302  // ----------------------------------------------------------------------
2303 
2304  QStringList allowedOrigins;
2305 
2306  int serverStatusPort = gCoreContext->GetMasterServerStatusPort();
2307  int backendSSLPort = gCoreContext->GetNumSetting( "BackendSSLPort",
2308  serverStatusPort + 10);
2309 
2310  QString masterAddrPort = QString("%1:%2")
2312  .arg(serverStatusPort);
2313  QString masterTLSAddrPort = QString("%1:%2")
2315  .arg(backendSSLPort);
2316 
2317  allowedOrigins << QString("http://%1").arg(masterAddrPort);
2318  allowedOrigins << QString("https://%2").arg(masterTLSAddrPort);
2319 
2320  QString localhostname = QHostInfo::localHostName();
2321  if (!localhostname.isEmpty())
2322  {
2323  allowedOrigins << QString("http://%1:%2")
2324  .arg(localhostname).arg(serverStatusPort);
2325  allowedOrigins << QString("https://%1:%2")
2326  .arg(localhostname).arg(backendSSLPort);
2327  }
2328 
2329  QStringList allowedOriginsList =
2330  gCoreContext->GetSetting("AllowedOriginsList", QString(
2331  "https://chromecast.mythtv.org,"
2332  "http://chromecast.mythtvcast.com"
2333  )).split(",");
2334 
2335  for (const auto & origin : qAsConst(allowedOriginsList))
2336  {
2337  if (origin.isEmpty())
2338  continue;
2339 
2340  if (origin == "*" || (!origin.startsWith("http://") &&
2341  !origin.startsWith("https://")))
2342  {
2343  LOG(VB_GENERAL, LOG_ERR, QString("Illegal AllowedOriginsList"
2344  " entry '%1'. Must start with http[s]:// and not be *")
2345  .arg(origin));
2346  }
2347  else
2348  {
2349  allowedOrigins << origin;
2350  }
2351  }
2352 
2353  if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_DEBUG))
2354  {
2355  for (const auto & origin : qAsConst(allowedOrigins))
2356  LOG(VB_HTTP, LOG_DEBUG, QString("Will allow Origin: %1").arg(origin));
2357  }
2358 
2359  if (allowedOrigins.contains(sOrigin))
2360  {
2361  SetResponseHeader( "Access-Control-Allow-Origin" , sOrigin);
2362  SetResponseHeader( "Access-Control-Allow-Credentials" , "true");
2363  SetResponseHeader( "Access-Control-Allow-Headers" , "Content-Type");
2364  LOG(VB_HTTP, LOG_DEBUG, QString("Allow-Origin: %1)").arg(sOrigin));
2365  }
2366  else
2367  {
2368  LOG(VB_GENERAL, LOG_CRIT, QString("HTTPRequest: Cross-origin request "
2369  "received with origin (%1)")
2370  .arg(sOrigin));
2371  }
2372 }
2373 
2376 //
2377 // BufferedSocketDeviceRequest Class Implementation
2378 //
2381 
2383 {
2384  QString sLine;
2385 
2386  if (m_pSocket && m_pSocket->isValid() &&
2387  m_pSocket->state() == QAbstractSocket::ConnectedState)
2388  {
2389  bool timeout = false;
2390  MythTimer timer;
2391  timer.start();
2392  while (!m_pSocket->canReadLine() && !timeout)
2393  {
2394  timeout = !(m_pSocket->waitForReadyRead( msecs ));
2395 
2396  if ( timer.elapsed() >= msecs )
2397  {
2398  timeout = true;
2399  LOG(VB_HTTP, LOG_INFO, "BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2400  }
2401  }
2402 
2403  if (!timeout)
2404  sLine = m_pSocket->readLine();
2405  }
2406 
2407  return( sLine );
2408 }
2409 
2411 //
2413 
2414 qint64 BufferedSocketDeviceRequest::ReadBlock(char *pData, qint64 nMaxLen,
2415  int msecs)
2416 {
2417  if (m_pSocket && m_pSocket->isValid() &&
2418  m_pSocket->state() == QAbstractSocket::ConnectedState)
2419  {
2420  if (msecs == 0)
2421  return( m_pSocket->read( pData, nMaxLen ));
2422 
2423  bool bTimeout = false;
2424  MythTimer timer;
2425  timer.start();
2426  while ( (m_pSocket->bytesAvailable() < (int)nMaxLen) && !bTimeout ) // This can end up waiting far longer than msecs
2427  {
2428  bTimeout = !(m_pSocket->waitForReadyRead( msecs ));
2429 
2430  if ( timer.elapsed() >= msecs )
2431  {
2432  bTimeout = true;
2433  LOG(VB_HTTP, LOG_INFO, "BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2434  }
2435  }
2436 
2437  // Just return what we have even if timed out.
2438 
2439  return( m_pSocket->read( pData, nMaxLen ));
2440  }
2441 
2442  return( -1 );
2443 }
2444 
2446 //
2448 
2449 qint64 BufferedSocketDeviceRequest::WriteBlock(const char *pData, qint64 nLen)
2450 {
2451  qint64 bytesWritten = -1;
2452  if (m_pSocket && m_pSocket->isValid() &&
2453  m_pSocket->state() == QAbstractSocket::ConnectedState)
2454  {
2455  bytesWritten = m_pSocket->write( pData, nLen );
2456  m_pSocket->waitForBytesWritten();
2457  }
2458 
2459  return( bytesWritten );
2460 }
2461 
2463 //
2465 
2467 {
2468  return( m_pSocket->localAddress().toString() );
2469 }
2470 
2472 //
2474 
2476 {
2477  return( m_pSocket->localPort() );
2478 }
2479 
2480 
2482 //
2484 
2486 {
2487  return( m_pSocket->peerAddress().toString() );
2488 }
2489 
jsonSerializer.h
MythSessionManager::IsValidUser
static bool IsValidUser(const QString &username)
Check if the given user exists but not whether there is a valid session open for them!
Definition: mythsession.cpp:151
ContentType_XML
@ ContentType_XML
Definition: httprequest.h:69
build_compdb.args
args
Definition: build_compdb.py:11
HTTPRequest::m_sBaseUrl
QString m_sBaseUrl
Definition: httprequest.h:125
e
QDomElement e
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1420
MythSessionManager::GetPasswordDigest
static QString GetPasswordDigest(const QString &username)
Load the password digest for comparison in the HTTP Auth code.
Definition: mythsession.cpp:224
HTTPRequest::SetContentType
HttpContentType SetContentType(const QString &sType)
Definition: httprequest.cpp:940
MythDate::toString
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:80
HTTPRequest::FormatRawResponse
void FormatRawResponse(const QString &sXML)
Definition: httprequest.cpp:840
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
ContentType_Unknown
@ ContentType_Unknown
Definition: httprequest.h:67
HTTPRequest::SetRequestProtocol
void SetRequestProtocol(const QString &sLine)
Definition: httprequest.cpp:895
ContentType_Urlencoded
@ ContentType_Urlencoded
Definition: httprequest.h:68
ResponseTypeCSS
@ ResponseTypeCSS
Definition: httprequest.h:79
Serializer
Definition: serializer.h:32
HttpServer::GetServerVersion
static QString GetServerVersion(void)
Definition: httpserver.cpp:285
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
HTTPRequest::ParseRequest
bool ParseRequest()
Definition: httprequest.cpp:1258
doc
QDomDocument doc("MYTHARCHIVEITEM")
BufferedSocketDeviceRequest::GetHostPort
quint16 GetHostPort() override
Definition: httprequest.cpp:2475
XmlPListSerializer
Definition: xmlplistSerializer.h:13
BufferedSocketDeviceRequest::GetPeerAddress
QString GetPeerAddress() override
Definition: httprequest.cpp:2485
HTTPRequest::m_sMethod
QString m_sMethod
Definition: httprequest.h:127
HTTPRequest::m_sResourceUrl
QString m_sResourceUrl
Definition: httprequest.h:126
BufferedSocketDeviceRequest::GetHostAddress
QString GetHostAddress() override
Definition: httprequest.cpp:2466
ResponseTypeUnknown
@ ResponseTypeUnknown
Definition: httprequest.h:75
JSONSerializer
Definition: jsonSerializer.h:32
HTTPRequest::IsEncrypted
bool IsEncrypted() const
Definition: httprequest.h:196
HTTPRequest::m_sProtocol
QString m_sProtocol
Definition: httprequest.h:135
mythcoreutil.h
HTTPRequest::m_sResponseTypeText
QString m_sResponseTypeText
Definition: httprequest.h:148
HTTPRequest::GetResponseProtocol
static QString GetResponseProtocol()
Definition: httprequest.cpp:919
HTTPRequest::m_sOriginalUrl
QString m_sOriginalUrl
Definition: httprequest.h:123
HTTPRequest::GetPeerAddress
virtual QString GetPeerAddress()=0
HTTPRequest::GetSupportedMimeTypes
static QStringList GetSupportedMimeTypes()
Definition: httprequest.cpp:1060
arg
arg(title).arg(filename).arg(doDelete))
MythDate::kOverrideUTC
@ kOverrideUTC
Present date/time in UTC.
Definition: mythdate.h:28
ResponseTypeJS
@ ResponseTypeJS
Definition: httprequest.h:78
ResponseTypeNone
@ ResponseTypeNone
Definition: httprequest.h:74
RequestTypePost
@ RequestTypePost
Definition: httprequest.h:49
HTTPRequest::Authenticated
bool Authenticated()
Definition: httprequest.cpp:2133
HTTPRequest::SendResponseFile
qint64 SendResponseFile(const QString &sFileName)
Definition: httprequest.cpp:491
RequestTypeOptions
@ RequestTypeOptions
Definition: httprequest.h:53
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
RequestTypeResponse
@ RequestTypeResponse
Definition: httprequest.h:61
HTTPRequest::DigestAuthentication
bool DigestAuthentication()
Definition: httprequest.cpp:1972
HTTPRequest::m_userSession
MythUserSession m_userSession
Definition: httprequest.h:160
gzipCompress
QByteArray gzipCompress(const QByteArray &data)
Definition: mythcoreutil.cpp:172
HTTPRequest::GetRequestHeader
QString GetRequestHeader(const QString &sKey, QString sDefault)
Definition: httprequest.cpp:1172
build_compdb.file
file
Definition: build_compdb.py:55
HTTPRequest::BasicAuthentication
bool BasicAuthentication()
Definition: httprequest.cpp:1913
HTTPRequest::m_bProtected
bool m_bProtected
Definition: httprequest.h:139
RequestTypeSubscribe
@ RequestTypeSubscribe
Definition: httprequest.h:57
HTTPRequest::TestMimeType
static QString TestMimeType(const QString &sFileName)
Definition: httprequest.cpp:1077
HTTPRequest::Encode
static QString Encode(const QString &sIn)
Definition: httprequest.cpp:1801
HTTPRequest::SetCookie
void SetCookie(const QString &sKey, const QString &sValue, const QDateTime &expiryDate, bool secure)
Definition: httprequest.cpp:2169
RequestTypeUnsubscribe
@ RequestTypeUnsubscribe
Definition: httprequest.h:58
HTTPRequest::m_procReqLineExp
QRegularExpression m_procReqLineExp
Definition: httprequest.h:113
xmlSerializer.h
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
HTTPRequest::Decode
static QString Decode(const QString &sIn)
Definition: httprequest.cpp:1825
HTTPRequest::GetSerializer
Serializer * GetSerializer()
Definition: httprequest.cpp:1764
MythCoreContext::GetMasterServerStatusPort
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
Definition: mythcorecontext.cpp:1010
SoapSerializer
Definition: soapSerializer.h:28
HTTPRequest::GetResponseHeaders
QString GetResponseHeaders(void)
Definition: httprequest.cpp:1187
HTTPRequest::ExtractMethodFromURL
void ExtractMethodFromURL()
Definition: httprequest.cpp:1625
HTTPRequest::m_sPrivateToken
QString m_sPrivateToken
Definition: httprequest.h:159
mythversion.h
HTTPRequest::m_nMajor
int m_nMajor
Definition: httprequest.h:136
ResponseTypeFile
@ ResponseTypeFile
Definition: httprequest.h:82
MythUserSession
Definition: mythsession.h:17
ResponseTypeHeader
@ ResponseTypeHeader
Definition: httprequest.h:84
HTTPRequest::m_mapRespHeaders
QStringMap m_mapRespHeaders
Definition: httprequest.h:151
HTTPRequest::WriteBlock
virtual qint64 WriteBlock(const char *pData, qint64 nLen)=0
NameValues
Definition: upnputil.h:89
HTTPRequest::FormatActionResponse
void FormatActionResponse(Serializer *ser)
Definition: httprequest.cpp:767
MythSessionManager::GetSession
MythUserSession GetSession(const QString &sessionToken)
Load the session details and return.
Definition: mythsession.cpp:175
hardwareprofile.smolt.secure
secure
Definition: smolt.py:95
Serializer::GetContentType
virtual QString GetContentType()=0
mythdate.h
HTTPRequest::s_szServerHeaders
static const char * s_szServerHeaders
Definition: httprequest.h:111
upnp.h
HTTPRequest::m_nResponseStatus
long m_nResponseStatus
Definition: httprequest.h:150
mythlogging.h
HttpContentType
HttpContentType
Definition: httprequest.h:66
RequestTypeMSearch
@ RequestTypeMSearch
Definition: httprequest.h:56
HTTPRequest::GetResponsePage
QByteArray GetResponsePage(void)
Definition: httprequest.cpp:1012
XmlSerializer
Definition: xmlSerializer.h:33
MythCoreContext::GetMasterServerIP
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
Definition: mythcorecontext.cpp:983
HTTPRequest::GetMimeType
static QString GetMimeType(const QString &sFileExtension)
Definition: httprequest.cpp:1041
MythUserSession::IsValid
bool IsValid(void) const
Check if this session object appears properly constructed, it DOES NOT validate whether it is a valid...
Definition: mythsession.cpp:15
HTTPRequest::m_mapParams
QStringMap m_mapParams
Definition: httprequest.h:129
HTTPRequest::m_parseRangeExp
QRegularExpression m_parseRangeExp
Definition: httprequest.h:114
compat.h
QStringMap
QMap< QString, QString > QStringMap
Definition: upnputil.h:44
RequestTypeHead
@ RequestTypeHead
Definition: httprequest.h:48
HTTPRequest::BuildResponseHeader
QString BuildResponseHeader(long long nSize)
Definition: httprequest.cpp:200
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
Serializer::AddHeaders
virtual void AddHeaders(QStringMap &headers)
Definition: serializer.cpp:22
HTTPRequest::m_bKeepAlive
bool m_bKeepAlive
Definition: httprequest.h:164
HTTPRequest::m_sRequestUrl
QString m_sRequestUrl
Definition: httprequest.h:124
ResponseTypeXML
@ ResponseTypeXML
Definition: httprequest.h:76
MythUserSession::GetSessionToken
QString GetSessionToken(void) const
Definition: mythsession.h:38
HTTPRequest::ParseRange
bool ParseRange(QString sRange, long long llSize, long long *pllStart, long long *pllEnd)
Definition: httprequest.cpp:1521
HTTPRequest::GetETagHash
static QString GetETagHash(const QByteArray &data)
Definition: httprequest.cpp:1841
HTTPRequest::m_sRawRequest
QString m_sRawRequest
Definition: httprequest.h:121
HTTPRequest::ProcessRequestLine
void ProcessRequestLine(const QString &sLine)
Definition: httprequest.cpp:1450
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:56
SENDFILE_BUFFER_SIZE
#define SENDFILE_BUFFER_SIZE
Definition: httprequest.cpp:665
HTTPRequest::m_mapHeaders
QStringMultiMap m_mapHeaders
Definition: httprequest.h:130
ResponseTypeOther
@ ResponseTypeOther
Definition: httprequest.h:83
HTTPRequest::GetHostName
virtual QString GetHostName()
Definition: httprequest.cpp:2210
MythUserSession::GetSessionExpires
QDateTime GetSessionExpires() const
Definition: mythsession.h:43
SOAP_ENVELOPE_END
#define SOAP_ENVELOPE_END
Definition: httprequest.h:36
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:929
HTTPRequest::SendData
qint64 SendData(QIODevice *pDevice, qint64 llStart, qint64 llBytes)
Definition: httprequest.cpp:667
HTTPRequest::m_sFileName
QString m_sFileName
Definition: httprequest.h:153
HTTPRequest::GetResponseStatus
QString GetResponseStatus(void) const
Definition: httprequest.cpp:958
ResponseTypeHTML
@ ResponseTypeHTML
Definition: httprequest.h:77
MythSessionManager::LoginUser
MythUserSession LoginUser(const QString &username, const QByteArray &digest, const QString &client="")
Login user by digest.
Definition: mythsession.cpp:282
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
HTTPRequest::ParseCookies
void ParseCookies(void)
Definition: httprequest.cpp:1240
HTTPRequest::m_bSOAPRequest
bool m_bSOAPRequest
Definition: httprequest.h:142
MythTimer::elapsed
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
HTTPRequest::CalculateDigestNonce
QString CalculateDigestNonce(const QString &timeStamp) const
Definition: httprequest.cpp:1901
ResponseTypeText
@ ResponseTypeText
Definition: httprequest.h:80
RequestTypeNotify
@ RequestTypeNotify
Definition: httprequest.h:59
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
BufferedSocketDeviceRequest::WriteBlock
qint64 WriteBlock(const char *pData, qint64 nLen) override
Definition: httprequest.cpp:2449
HTTPRequest::FormatErrorResponse
void FormatErrorResponse(bool bServerError, const QString &sFaultString, const QString &sDetails)
Definition: httprequest.cpp:729
HTTPRequest::GetLastHeader
QString GetLastHeader(const QString &sType) const
Definition: httprequest.cpp:161
mythcorecontext.h
HTTPRequest::FormatFileResponse
void FormatFileResponse(const QString &sFileName)
Definition: httprequest.cpp:855
SOAP_ENVELOPE_BEGIN
#define SOAP_ENVELOPE_BEGIN
Definition: httprequest.h:33
HTTPRequest::SetResponseHeader
void SetResponseHeader(const QString &sKey, const QString &sValue, bool replace=false)
Definition: httprequest.cpp:2156
MythDate::kRFC822
@ kRFC822
HTTP Date format.
Definition: mythdate.h:27
HTTPRequest::m_eContentType
HttpContentType m_eContentType
Definition: httprequest.h:119
HTTPRequest::SendFile
qint64 SendFile(QFile &file, qint64 llStart, qint64 llBytes)
Definition: httprequest.cpp:717
MythCoreContext::GetLanguageAndVariant
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
Definition: mythcorecontext.cpp:1790
HTTPRequest::getSocketHandle
virtual int getSocketHandle()=0
g_MIMETypes
static std::array< const MIMETypes, 63 > g_MIMETypes
Definition: httprequest.cpp:63
HTTPRequest::m_eResponseType
HttpResponseType m_eResponseType
Definition: httprequest.h:147
HTTPRequest::m_nMinor
int m_nMinor
Definition: httprequest.h:137
HTTPRequest::m_sPayload
QString m_sPayload
Definition: httprequest.h:133
HTTPRequest::GetRequestProtocol
QString GetRequestProtocol() const
Definition: httprequest.cpp:908
StaticPage
static QString StaticPage
Definition: httprequest.cpp:140
Configuration::GetValue
virtual int GetValue(const QString &sSetting, int Default)=0
HTTPRequest::GetParameters
static long GetParameters(QString sParams, QStringMap &mapParams)
Definition: httprequest.cpp:1128
MythDate::current_iso_string
QString current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
Definition: mythdate.cpp:18
HTTPRequest::m_sNameSpace
QString m_sNameSpace
Definition: httprequest.h:143
UPnp::GetConfiguration
static Configuration * GetConfiguration()
Definition: upnp.cpp:84
mythtimer.h
HTTPRequest::AddCORSHeaders
void AddCORSHeaders(const QString &sOrigin)
Definition: httprequest.cpp:2276
RequestTypeUnknown
@ RequestTypeUnknown
Definition: httprequest.h:45
BufferedSocketDeviceRequest::m_pSocket
QTcpSocket * m_pSocket
Definition: httprequest.h:280
HTTPRequest::GetResponseType
QString GetResponseType(void) const
Definition: httprequest.cpp:1021
musicbrainzngs.caa.hostname
string hostname
Definition: caa.py:17
HTTPRequest::m_nKeepAliveTimeout
uint m_nKeepAliveTimeout
Definition: httprequest.h:165
HTTPRequest::SendResponse
qint64 SendResponse(void)
Definition: httprequest.cpp:308
HTTPRequest::ProcessSOAPPayload
bool ProcessSOAPPayload(const QString &sSOAPAction)
Definition: httprequest.cpp:1656
HTTPRequest::GetAuthenticationHeader
QString GetAuthenticationHeader(bool isStale=false)
Definition: httprequest.cpp:1871
MythCoreContext::GetSessionManager
MythSessionManager * GetSessionManager(void)
Definition: mythcorecontext.cpp:2050
xmlplistSerializer.h
HTTPRequest::IsUrlProtected
static bool IsUrlProtected(const QString &sBaseUrl)
Definition: httprequest.cpp:1852
ResponseTypeSVG
@ ResponseTypeSVG
Definition: httprequest.h:81
HTTPRequest::GetHostAddress
virtual QString GetHostAddress()=0
HTTPRequest::ReadBlock
virtual qint64 ReadBlock(char *pData, qint64 nMaxLen, int msecs=0)=0
RequestTypeGet
@ RequestTypeGet
Definition: httprequest.h:47
httprequest.h
BufferedSocketDeviceRequest::ReadLine
QString ReadLine(int msecs) override
Definition: httprequest.cpp:2382
HTTPRequest::m_response
QBuffer m_response
Definition: httprequest.h:155
HTTPRequest::ReadLine
virtual QString ReadLine(int msecs)=0
HTTPRequest::ParseKeepAlive
bool ParseKeepAlive(void)
Definition: httprequest.cpp:1206
HTTPRequest::SetRequestType
HttpRequestType SetRequestType(const QString &sType)
Definition: httprequest.cpp:173
HttpRequestType
HttpRequestType
Definition: httprequest.h:44
HTTPRequest::GetRequestType
QString GetRequestType() const
Definition: httprequest.cpp:2236
soapSerializer.h
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:915
BufferedSocketDeviceRequest::ReadBlock
qint64 ReadBlock(char *pData, qint64 nMaxLen, int msecs=0) override
Definition: httprequest.cpp:2414
MythSessionManager
We use digest authentication because it protects the password over unprotected networks.
Definition: mythsession.h:99
HTTPRequest::m_eType
HttpRequestType m_eType
Definition: httprequest.h:118
HTTPRequest::m_mapCookies
QStringMap m_mapCookies
Definition: httprequest.h:131