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