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