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 == 0) // Value wasn't passed in by the server, so go with the configured value
224  m_nKeepAliveTimeout = gCoreContext->GetNumSetting("HTTP/KeepAliveTimeoutSecs", 10);
225  SetResponseHeader("Keep-Alive", QString("timeout=%1").arg(m_nKeepAliveTimeout));
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:")
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:")
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")
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 
955 }
956 
957 
959 //
961 
962 QString HTTPRequest::GetResponseStatus( void ) const
963 {
964  switch( m_nResponseStatus )
965  {
966  case 200: return( "200 OK" );
967  case 201: return( "201 Created" );
968  case 202: return( "202 Accepted" );
969  case 204: return( "204 No Content" );
970  case 205: return( "205 Reset Content" );
971  case 206: return( "206 Partial Content" );
972  case 300: return( "300 Multiple Choices" );
973  case 301: return( "301 Moved Permanently" );
974  case 302: return( "302 Found" );
975  case 303: return( "303 See Other" );
976  case 304: return( "304 Not Modified" );
977  case 305: return( "305 Use Proxy" );
978  case 307: return( "307 Temporary Redirect" );
979  case 308: return( "308 Permanent Redirect" );
980  case 400: return( "400 Bad Request" );
981  case 401: return( "401 Unauthorized" );
982  case 403: return( "403 Forbidden" );
983  case 404: return( "404 Not Found" );
984  case 405: return( "405 Method Not Allowed" );
985  case 406: return( "406 Not Acceptable" );
986  case 408: return( "408 Request Timeout" );
987  case 410: return( "410 Gone" );
988  case 411: return( "411 Length Required" );
989  case 412: return( "412 Precondition Failed" );
990  case 413: return( "413 Request Entity Too Large" );
991  case 414: return( "414 Request-URI Too Long" );
992  case 415: return( "415 Unsupported Media Type" );
993  case 416: return( "416 Requested Range Not Satisfiable" );
994  case 417: return( "417 Expectation Failed" );
995  // I'm a teapot
996  case 428: return( "428 Precondition Required" ); // RFC 6585
997  case 429: return( "429 Too Many Requests" ); // RFC 6585
998  case 431: return( "431 Request Header Fields Too Large" ); // RFC 6585
999  case 500: return( "500 Internal Server Error" );
1000  case 501: return( "501 Not Implemented" );
1001  case 502: return( "502 Bad Gateway" );
1002  case 503: return( "503 Service Unavailable" );
1003  case 504: return( "504 Gateway Timeout" );
1004  case 505: return( "505 HTTP Version Not Supported" );
1005  case 510: return( "510 Not Extended" );
1006  case 511: return( "511 Network Authentication Required" ); // RFC 6585
1007  }
1008 
1009  return( QString( "%1 Unknown" ).arg( m_nResponseStatus ));
1010 }
1011 
1013 //
1015 
1017 {
1018  return StaticPage.arg(QString::number(m_nResponseStatus)).arg(GetResponseStatus()).toUtf8();
1019 }
1020 
1022 //
1024 
1025 QString HTTPRequest::GetResponseType( void ) const
1026 {
1027  switch( m_eResponseType )
1028  {
1029  case ResponseTypeXML : return( "text/xml; charset=\"UTF-8\"" );
1030  case ResponseTypeHTML : return( "text/html; charset=\"UTF-8\"" );
1031  case ResponseTypeCSS : return( "text/css; charset=\"UTF-8\"" );
1032  case ResponseTypeJS : return( "application/javascript" );
1033  case ResponseTypeText : return( "text/plain; charset=\"UTF-8\"" );
1034  case ResponseTypeSVG : return( "image/svg+xml" );
1035  default: break;
1036  }
1037 
1038  return( "text/plain" );
1039 }
1040 
1042 //
1044 
1045 QString HTTPRequest::GetMimeType( const QString &sFileExtension )
1046 {
1047  QString ext;
1048 
1049  for (const auto & type : g_MIMETypes)
1050  {
1051  ext = type.pszExtension;
1052 
1053  if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
1054  return( type.pszType );
1055  }
1056 
1057  return( "text/plain" );
1058 }
1059 
1061 //
1063 
1065 {
1066  QStringList mimeTypes;
1067 
1068  for (const auto & type : g_MIMETypes)
1069  {
1070  if (!mimeTypes.contains( type.pszType ))
1071  mimeTypes.append( type.pszType );
1072  }
1073 
1074  return mimeTypes;
1075 }
1076 
1078 //
1080 
1081 QString HTTPRequest::TestMimeType( const QString &sFileName )
1082 {
1083  QFileInfo info( sFileName );
1084  QString sLOC = "HTTPRequest::TestMimeType(" + sFileName + ") - ";
1085  QString sSuffix = info.suffix().toLower();
1086  QString sMIME = GetMimeType( sSuffix );
1087 
1088  if ( sSuffix == "nuv" ) // If a very old recording, might be an MPEG?
1089  {
1090  // Read the header to find out:
1091  QFile file( sFileName );
1092 
1093  if ( file.open(QIODevice::ReadOnly | QIODevice::Text) )
1094  {
1095  QByteArray head = file.read(8);
1096  QString sHex = head.toHex();
1097 
1098  LOG(VB_HTTP, LOG_DEBUG, sLOC + "file starts with " + sHex);
1099 
1100  if ( sHex == "000001ba44000400" ) // MPEG2 PS
1101  sMIME = "video/mp2p";
1102 
1103  if ( head == "MythTVVi" )
1104  {
1105  file.seek(100);
1106  head = file.read(4);
1107 
1108  if ( head == "DIVX" )
1109  {
1110  LOG(VB_HTTP, LOG_DEBUG, sLOC + "('MythTVVi...DIVXLAME')");
1111  sMIME = "video/mp4";
1112  }
1113  // NuppelVideo is "RJPG" at byte 612
1114  // We could also check the audio (LAME or RAWA),
1115  // but since most UPnP clients choke on Nuppel, no need
1116  }
1117 
1118  file.close();
1119  }
1120  else
1121  LOG(VB_GENERAL, LOG_ERR, sLOC + "Could not read file");
1122  }
1123 
1124  LOG(VB_HTTP, LOG_INFO, sLOC + "type is " + sMIME);
1125  return sMIME;
1126 }
1127 
1129 //
1131 
1132 long HTTPRequest::GetParameters( QString sParams, QStringMap &mapParams )
1133 {
1134  long nCount = 0;
1135 
1136  LOG(VB_HTTP, LOG_INFO, QString("sParams: '%1'").arg(sParams));
1137 
1138  // This looks odd, but it is here to cope with stupid UPnP clients that
1139  // forget to de-escape the URLs. We can't map %26 here as well, as that
1140  // breaks anything that is trying to pass & as part of a name or value.
1141  sParams.replace( "&amp;", "&" );
1142 
1143  if (!sParams.isEmpty())
1144  {
1145 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1146  QStringList params = sParams.split('&', QString::SkipEmptyParts);
1147 #else
1148  QStringList params = sParams.split('&', Qt::SkipEmptyParts);
1149 #endif
1150 
1151  for (const auto & param : qAsConst(params))
1152  {
1153  QString sName = param.section( '=', 0, 0 );
1154  QString sValue = param.section( '=', 1 );
1155  sValue.replace("+"," ");
1156 
1157  if (!sName.isEmpty())
1158  {
1159  sName = QUrl::fromPercentEncoding(sName.toUtf8());
1160  sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1161 
1162  mapParams.insert( sName.trimmed(), sValue );
1163  nCount++;
1164  }
1165  }
1166  }
1167 
1168  return nCount;
1169 }
1170 
1171 
1173 //
1175 
1176 QString HTTPRequest::GetRequestHeader( const QString &sKey, const QString &sDefault )
1177 {
1178  QStringMap::iterator it = m_mapHeaders.find( sKey.toLower() );
1179 
1180  if ( it == m_mapHeaders.end())
1181  return( sDefault );
1182 
1183  return *it;
1184 }
1185 
1186 
1188 //
1190 
1192 {
1193  QString sHeader = s_szServerHeaders;
1194 
1195  for ( QStringMap::iterator it = m_mapRespHeaders.begin();
1196  it != m_mapRespHeaders.end();
1197  ++it )
1198  {
1199  sHeader += it.key() + ": ";
1200  sHeader += *it + "\r\n";
1201  }
1202 
1203  return( sHeader );
1204 }
1205 
1207 //
1209 
1211 {
1212  // TODO: Think about whether we should use a longer timeout if the client
1213  // has explicitly specified 'Keep-alive'
1214 
1215  // HTTP 1.1 ... server may assume keep-alive
1216  bool bKeepAlive = true;
1217 
1218  // if HTTP/1.0... must default to false
1219  if ((m_nMajor == 1) && (m_nMinor == 0))
1220  bKeepAlive = false;
1221 
1222  // Read Connection Header to see whether the client has explicitly
1223  // asked for the connection to be kept alive or closed after the response
1224  // is sent
1225  QString sConnection = GetRequestHeader( "connection", "default" ).toLower();
1226 
1227  QStringList sValueList = sConnection.split(",");
1228 
1229  if ( sValueList.contains("close") )
1230  {
1231  LOG(VB_HTTP, LOG_DEBUG, "Client requested the connection be closed");
1232  bKeepAlive = false;
1233  }
1234  else if (sValueList.contains("keep-alive"))
1235  bKeepAlive = true;
1236 
1237  return bKeepAlive;
1238 }
1239 
1241 //
1243 
1245 {
1246  QStringList sCookieList = m_mapHeaders.values("cookie");
1247 
1248  QStringList::iterator it;
1249  for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1250  {
1251  QString key = (*it).section('=', 0, 0);
1252  QString value = (*it).section('=', 1);
1253 
1254  m_mapCookies.insert(key, value);
1255  }
1256 }
1257 
1259 //
1261 
1263 {
1264  bool bSuccess = false;
1265 
1266  try
1267  {
1268  // Read first line to determine requestType
1269  QString sRequestLine = ReadLine( 2000 );
1270 
1271  if ( sRequestLine.isEmpty() )
1272  {
1273  LOG(VB_GENERAL, LOG_ERR, "Timeout reading first line of request." );
1274  return false;
1275  }
1276 
1277  // -=>TODO: Should read lines until a valid request???
1278  ProcessRequestLine( sRequestLine );
1279 
1280  if (m_nMajor > 1 || m_nMajor < 0)
1281  {
1283  m_nResponseStatus = 505;
1284  m_response.write( GetResponsePage() );
1285 
1286  return true;
1287  }
1288 
1289  if (m_eType == RequestTypeUnknown)
1290  {
1292  m_nResponseStatus = 501; // Not Implemented
1293  // Conservative list, we can't really know what methods we
1294  // actually allow for an arbitrary resource without some sort of
1295  // high maintenance database
1296  SetResponseHeader("Allow", "GET, HEAD");
1297  m_response.write( GetResponsePage() );
1298  return true;
1299  }
1300 
1301  // Read Header
1302  bool bDone = false;
1303  QString sLine = ReadLine( 2000 );
1304 
1305  while (( !sLine.isEmpty() ) && !bDone )
1306  {
1307  if (sLine != "\r\n")
1308  {
1309  QString sName = sLine.section( ':', 0, 0 ).trimmed();
1310  QString sValue = sLine.section( ':', 1 );
1311 
1312  sValue.truncate( sValue.length() - 2 );
1313 
1314  if (!sName.isEmpty() && !sValue.isEmpty())
1315  {
1316  m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1317  }
1318 
1319  sLine = ReadLine( 2000 );
1320  }
1321  else
1322  bDone = true;
1323  }
1324 
1325  // Dump request header
1326  for ( QStringMap::iterator it = m_mapHeaders.begin();
1327  it != m_mapHeaders.end();
1328  ++it )
1329  {
1330  LOG(VB_HTTP, LOG_INFO, QString("(Request Header) %1: %2")
1331  .arg(it.key()).arg(*it));
1332  }
1333 
1334  // Parse Cookies
1335  ParseCookies();
1336 
1337  // Parse out keep alive
1339 
1340  // Check to see if we found the end of the header or we timed out.
1341  if (!bDone)
1342  {
1343  LOG(VB_GENERAL, LOG_INFO, "Timeout waiting for request header." );
1344  return false;
1345  }
1346 
1347  // HTTP/1.1 requires that the Host header be present, even if empty
1348  if ((m_nMinor == 1) && !m_mapHeaders.contains("host"))
1349  {
1351  m_nResponseStatus = 400;
1352  m_response.write( GetResponsePage() );
1353 
1354  return true;
1355  }
1356 
1357  // Destroy session if requested
1358  if (m_mapHeaders.contains("x-myth-clear-session"))
1359  {
1360  SetCookie("sessionToken", "", MythDate::current().addDays(-2), true);
1361  m_mapCookies.remove("sessionToken");
1362  }
1363 
1364  // Allow session resumption for TLS connections
1365  if (m_mapCookies.contains("sessionToken"))
1366  {
1367  QString sessionToken = m_mapCookies["sessionToken"];
1368  MythSessionManager *sessionManager = gCoreContext->GetSessionManager();
1369  MythUserSession session = sessionManager->GetSession(sessionToken);
1370 
1371  if (session.IsValid())
1372  m_userSession = session;
1373  }
1374 
1375  if (IsUrlProtected( m_sBaseUrl ))
1376  {
1377  if (!Authenticated())
1378  {
1380  m_nResponseStatus = 401;
1381  m_response.write( GetResponsePage() );
1382  // Since this may not be the first attempt at authentication,
1383  // Authenticated may have set the header with the appropriate
1384  // stale attribute
1385  SetResponseHeader("WWW-Authenticate", GetAuthenticationHeader(false));
1386 
1387  return true;
1388  }
1389 
1390  m_bProtected = true;
1391  }
1392 
1393  bSuccess = true;
1394 
1395  SetContentType( GetLastHeader( "content-type" ) );
1396  // Lets load payload if any.
1397  long nPayloadSize = GetLastHeader( "content-length" ).toLong();
1398 
1399  if (nPayloadSize > 0)
1400  {
1401  char *pszPayload = new char[ nPayloadSize + 2 ];
1402  long nBytes = 0;
1403 
1404  nBytes = ReadBlock( pszPayload, nPayloadSize, 5000 );
1405  if (nBytes == nPayloadSize )
1406  {
1407  m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1408 
1409  // See if the payload is just data from a form post
1412  }
1413  else
1414  {
1415  LOG(VB_GENERAL, LOG_ERR,
1416  QString("Unable to read entire payload (read %1 of %2 bytes)")
1417  .arg( nBytes ) .arg( nPayloadSize ) );
1418  bSuccess = false;
1419  }
1420 
1421  delete [] pszPayload;
1422  }
1423 
1424  // Check to see if this is a SOAP encoded message
1425  QString sSOAPAction = GetRequestHeader( "SOAPACTION", "" );
1426 
1427  if (!sSOAPAction.isEmpty())
1428  bSuccess = ProcessSOAPPayload( sSOAPAction );
1429  else
1431 
1432 #if 0
1433  if (m_sMethod != "*" )
1434  LOG(VB_HTTP, LOG_DEBUG,
1435  QString("HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1436  "Method (%3) - Bytes in Socket Buffer (%4)")
1438  .arg(m_sMethod) .arg(BytesAvailable()));
1439 #endif
1440  }
1441  catch(...)
1442  {
1443  LOG(VB_GENERAL, LOG_WARNING,
1444  "Unexpected exception in HTTPRequest::ParseRequest" );
1445  }
1446 
1447  return bSuccess;
1448 }
1449 
1451 //
1453 
1454 void HTTPRequest::ProcessRequestLine( const QString &sLine )
1455 {
1456  m_sRawRequest = sLine;
1457 
1458 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1459  QStringList tokens = sLine.split(m_procReqLineExp, QString::SkipEmptyParts);
1460 #else
1461  QStringList tokens = sLine.split(m_procReqLineExp, Qt::SkipEmptyParts);
1462 #endif
1463  int nCount = tokens.count();
1464 
1465  // ----------------------------------------------------------------------
1466 
1467  if ( sLine.startsWith( QString("HTTP/") ))
1469  else
1471 
1472  // ----------------------------------------------------------------------
1473  // if this is actually a response, then sLine's format will be:
1474  // HTTP/m.n <response code> <response text>
1475  // otherwise:
1476  // <method> <Resource URI> HTTP/m.n
1477  // ----------------------------------------------------------------------
1478 
1480  {
1481  // ------------------------------------------------------------------
1482  // Process as a request
1483  // ------------------------------------------------------------------
1484 
1485  if (nCount > 0)
1486  SetRequestType( tokens[0].trimmed() );
1487 
1488  if (nCount > 1)
1489  {
1490  m_sOriginalUrl = tokens[1].toUtf8(); // Used by authorization check
1491  m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1492  m_sBaseUrl = m_sRequestUrl.section( '?', 0, 0).trimmed();
1493 
1494  m_sResourceUrl = m_sBaseUrl; // Save complete url without parameters
1495 
1496  // Process any Query String Parameters
1497  QString sQueryStr = tokens[1].section( '?', 1, 1 );
1498 
1499  if (!sQueryStr.isEmpty())
1500  GetParameters( sQueryStr, m_mapParams );
1501  }
1502 
1503  if (nCount > 2)
1504  SetRequestProtocol( tokens[2].trimmed() );
1505  }
1506  else
1507  {
1508  // ------------------------------------------------------------------
1509  // Process as a Response
1510  // ------------------------------------------------------------------
1511  if (nCount > 0)
1512  SetRequestProtocol( tokens[0].trimmed() );
1513 
1514  if (nCount > 1)
1515  m_nResponseStatus = tokens[1].toInt();
1516  }
1517 
1518 
1519 }
1520 
1522 //
1524 
1525 bool HTTPRequest::ParseRange( QString sRange,
1526  long long llSize,
1527  long long *pllStart,
1528  long long *pllEnd )
1529 {
1530  // ----------------------------------------------------------------------
1531  // -=>TODO: Only handle 1 range at this time...
1532  // should make work with full spec.
1533  // ----------------------------------------------------------------------
1534 
1535  if (sRange.isEmpty())
1536  return false;
1537 
1538  // ----------------------------------------------------------------------
1539  // remove any "bytes="
1540  // ----------------------------------------------------------------------
1541  int nIdx = sRange.indexOf(m_parseRangeExp);
1542 
1543  if (nIdx < 0)
1544  return false;
1545 
1546  if (nIdx > 0)
1547  sRange.remove( 0, nIdx );
1548 
1549  // ----------------------------------------------------------------------
1550  // Split multiple ranges
1551  // ----------------------------------------------------------------------
1552 
1553 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1554  QStringList ranges = sRange.split(',', QString::SkipEmptyParts);
1555 #else
1556  QStringList ranges = sRange.split(',', Qt::SkipEmptyParts);
1557 #endif
1558 
1559  if (ranges.count() == 0)
1560  return false;
1561 
1562  // ----------------------------------------------------------------------
1563  // Split first range into its components
1564  // ----------------------------------------------------------------------
1565 
1566  QStringList parts = ranges[0].split('-');
1567 
1568  if (parts.count() != 2)
1569  return false;
1570 
1571  if (parts[0].isEmpty() && parts[1].isEmpty())
1572  return false;
1573 
1574  // ----------------------------------------------------------------------
1575  //
1576  // ----------------------------------------------------------------------
1577 
1578  bool conv_ok = false;
1579  if (parts[0].isEmpty())
1580  {
1581  // ------------------------------------------------------------------
1582  // Does it match "-####"
1583  // ------------------------------------------------------------------
1584 
1585  long long llValue = parts[1].toLongLong(&conv_ok);
1586  if (!conv_ok) return false;
1587 
1588  *pllStart = llSize - llValue;
1589  *pllEnd = llSize - 1;
1590  }
1591  else if (parts[1].isEmpty())
1592  {
1593  // ------------------------------------------------------------------
1594  // Does it match "####-"
1595  // ------------------------------------------------------------------
1596 
1597  *pllStart = parts[0].toLongLong(&conv_ok);
1598 
1599  if (!conv_ok)
1600  return false;
1601 
1602  *pllEnd = llSize - 1;
1603  }
1604  else
1605  {
1606  // ------------------------------------------------------------------
1607  // Must be "####-####"
1608  // ------------------------------------------------------------------
1609 
1610  *pllStart = parts[0].toLongLong(&conv_ok);
1611  if (!conv_ok) return false;
1612  *pllEnd = parts[1].toLongLong(&conv_ok);
1613  if (!conv_ok) return false;
1614 
1615  if (*pllStart > *pllEnd)
1616  return false;
1617  }
1618 
1619  LOG(VB_HTTP, LOG_DEBUG, QString("%1 Range Requested %2 - %3")
1620  .arg(getSocketHandle()) .arg(*pllStart) .arg(*pllEnd));
1621 
1622  return true;
1623 }
1624 
1626 //
1628 
1630 {
1631  // Strip out leading http://192.168.1.1:6544/ -> /
1632  // Should fix #8678
1633  // FIXME what about https?
1634  QRegularExpression re {"^http[s]?://.*?/"};
1635  m_sBaseUrl.replace(re, "/");
1636 
1637 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1638  QStringList sList = m_sBaseUrl.split('/', QString::SkipEmptyParts);
1639 #else
1640  QStringList sList = m_sBaseUrl.split('/', Qt::SkipEmptyParts);
1641 #endif
1642 
1643  m_sMethod = "";
1644 
1645  if (!sList.isEmpty())
1646  {
1647  m_sMethod = sList.last();
1648  sList.pop_back();
1649  }
1650 
1651  m_sBaseUrl = '/' + sList.join( "/" );
1652  LOG(VB_HTTP, LOG_INFO, QString("ExtractMethodFromURL(end) : %1 : %2")
1654 }
1655 
1657 //
1659 
1660 bool HTTPRequest::ProcessSOAPPayload( const QString &sSOAPAction )
1661 {
1662  bool bSuccess = false;
1663 
1664  // ----------------------------------------------------------------------
1665  // Open Supplied XML uPnp Description file.
1666  // ----------------------------------------------------------------------
1667 
1668  LOG(VB_HTTP, LOG_INFO,
1669  QString("HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
1670  QDomDocument doc ( "request" );
1671 
1672  QString sErrMsg;
1673  int nErrLine = 0;
1674  int nErrCol = 0;
1675 
1676  if (!doc.setContent( m_sPayload, true, &sErrMsg, &nErrLine, &nErrCol ))
1677  {
1678  LOG(VB_GENERAL, LOG_ERR,
1679  QString( "Error parsing request at line: %1 column: %2 : %3" )
1680  .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
1681  return( false );
1682  }
1683 
1684  // --------------------------------------------------------------
1685  // XML Document Loaded... now parse it
1686  // --------------------------------------------------------------
1687 
1688  QString sService;
1689 
1690  if (sSOAPAction.contains( '#' ))
1691  {
1692  m_sNameSpace = sSOAPAction.section( '#', 0, 0).remove( 0, 1);
1693  m_sMethod = sSOAPAction.section( '#', 1 );
1694  m_sMethod.remove( m_sMethod.length()-1, 1 );
1695  }
1696  else
1697  {
1698  if (sSOAPAction.contains( '/' ))
1699  {
1700  int nPos = sSOAPAction.lastIndexOf( '/' );
1701  m_sNameSpace = sSOAPAction.mid(1, nPos);
1702  m_sMethod = sSOAPAction.mid(nPos + 1,
1703  sSOAPAction.length() - nPos - 2);
1704 
1705  nPos = m_sNameSpace.lastIndexOf( '/', -2);
1706  sService = m_sNameSpace.mid(nPos + 1,
1707  m_sNameSpace.length() - nPos - 2);
1708  m_sNameSpace = m_sNameSpace.mid( 0, nPos );
1709  }
1710  else
1711  {
1712  m_sNameSpace.clear();
1713  m_sMethod = sSOAPAction;
1714  m_sMethod.remove( QChar( '\"' ) );
1715  }
1716  }
1717 
1718  QDomNodeList oNodeList = doc.elementsByTagNameNS( m_sNameSpace, m_sMethod );
1719 
1720  if (oNodeList.count() == 0)
1721  {
1722  oNodeList =
1723  doc.elementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/",
1724  "Body");
1725  }
1726 
1727  if (oNodeList.count() > 0)
1728  {
1729  QDomNode oMethod = oNodeList.item(0);
1730 
1731  if (!oMethod.isNull())
1732  {
1733  m_bSOAPRequest = true;
1734 
1735  for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1736  oNode = oNode.nextSibling() )
1737  {
1738  QDomElement e = oNode.toElement();
1739 
1740  if (!e.isNull())
1741  {
1742  QString sName = e.tagName();
1743  QString sValue = "";
1744 
1745  QDomText oText = oNode.firstChild().toText();
1746 
1747  if (!oText.isNull())
1748  sValue = oText.nodeValue();
1749 
1750  sName = QUrl::fromPercentEncoding(sName.toUtf8());
1751  sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1752 
1753  m_mapParams.insert( sName.trimmed().toLower(), sValue );
1754  }
1755  }
1756 
1757  bSuccess = true;
1758  }
1759  }
1760 
1761  return bSuccess;
1762 }
1763 
1765 //
1767 
1769 {
1770  Serializer *pSerializer = nullptr;
1771 
1772  if (m_bSOAPRequest)
1773  {
1774  pSerializer = (Serializer *)new SoapSerializer(&m_response,
1776  }
1777  else
1778  {
1779  QString sAccept = GetRequestHeader( "Accept", "*/*" );
1780 
1781  if (sAccept.contains( "application/json", Qt::CaseInsensitive ) ||
1782  sAccept.contains( "text/javascript", Qt::CaseInsensitive ))
1783  {
1784  pSerializer = (Serializer *)new JSONSerializer(&m_response,
1785  m_sMethod);
1786  }
1787  else if (sAccept.contains( "text/x-apple-plist+xml", Qt::CaseInsensitive ))
1788  {
1789  pSerializer = (Serializer *)new XmlPListSerializer(&m_response);
1790  }
1791  }
1792 
1793  // Default to XML
1794 
1795  if (pSerializer == nullptr)
1796  pSerializer = (Serializer *)new XmlSerializer(&m_response, m_sMethod);
1797 
1798  return pSerializer;
1799 }
1800 
1802 //
1804 
1805 QString HTTPRequest::Encode(const QString &sIn)
1806 {
1807  QString sStr = sIn;
1808 #if 0
1809  LOG(VB_HTTP, LOG_DEBUG,
1810  QString("HTTPRequest::Encode Input : %1").arg(sStr));
1811 #endif
1812  sStr.replace('&', "&amp;" ); // This _must_ come first
1813  sStr.replace('<', "&lt;" );
1814  sStr.replace('>', "&gt;" );
1815  sStr.replace('"', "&quot;");
1816  sStr.replace("'", "&apos;");
1817 
1818 #if 0
1819  LOG(VB_HTTP, LOG_DEBUG,
1820  QString("HTTPRequest::Encode Output : %1").arg(sStr));
1821 #endif
1822  return sStr;
1823 }
1824 
1826 //
1828 
1829 QString HTTPRequest::Decode(const QString& sIn)
1830 {
1831  QString sStr = sIn;
1832  sStr.replace("&amp;", "&");
1833  sStr.replace("&lt;", "<");
1834  sStr.replace("&gt;", ">");
1835  sStr.replace("&quot;", "\"");
1836  sStr.replace("&apos;", "'");
1837 
1838  return sStr;
1839 }
1840 
1842 //
1844 
1845 QString HTTPRequest::GetETagHash(const QByteArray &data)
1846 {
1847  QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1848 
1849  return ("\"" + hash.toHex() + "\"");
1850 }
1851 
1853 //
1855 
1856 bool HTTPRequest::IsUrlProtected( const QString &sBaseUrl )
1857 {
1858  QString sProtected = UPnp::GetConfiguration()->GetValue( "HTTP/Protected/Urls", "/setup;/Config" );
1859 
1860  QStringList oList = sProtected.split( ';' );
1861 
1862  for( int nIdx = 0; nIdx < oList.count(); nIdx++)
1863  {
1864  if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
1865  return true;
1866  }
1867 
1868  return false;
1869 }
1870 
1872 //
1874 
1876 {
1877  QString authHeader;
1878 
1879  // For now we support a single realm, that will change
1880  QString realm = "MythTV";
1881 
1882  // Always use digest authentication where supported, it may be available
1883  // with HTTP 1.0 client as an extension, but we can't tell if that's the
1884  // case. It's guaranteed to be available for HTTP 1.1+
1885  if (m_nMajor >= 1 && m_nMinor > 0)
1886  {
1888  QString stale = isStale ? "true" : "false"; // FIXME
1889  authHeader = QString("Digest realm=\"%1\",nonce=\"%2\","
1890  "qop=\"auth\",stale=\"%3\",algorithm=\"MD5\"")
1891  .arg(realm).arg(nonce).arg(stale);
1892  }
1893  else
1894  {
1895  authHeader = QString("Basic realm=\"%1\"").arg(realm);
1896  }
1897 
1898  return authHeader;
1899 }
1900 
1902 //
1904 
1905 QString HTTPRequest::CalculateDigestNonce(const QString& timeStamp) const
1906 {
1907  QString uniqueID = QString("%1:%2").arg(timeStamp).arg(m_sPrivateToken);
1908  QString hash = QCryptographicHash::hash( uniqueID.toLatin1(), QCryptographicHash::Sha1).toHex(); // TODO: Change to Sha2 with QT5?
1909  QString nonce = QString("%1%2").arg(timeStamp).arg(hash); // Note: since this is going in a header it should avoid illegal chars
1910  return nonce;
1911 }
1912 
1914 //
1916 
1918 {
1919  LOG(VB_HTTP, LOG_NOTICE, "Attempting HTTP Basic Authentication");
1920  QStringList oList = GetLastHeader( "authorization" ).split( ' ' );
1921 
1922  if (m_nMajor == 1 && m_nMinor == 0) // We only support Basic auth for http 1.0 clients
1923  {
1924  LOG(VB_GENERAL, LOG_WARNING, "Basic authentication is only allowed for HTTP 1.0");
1925  return false;
1926  }
1927 
1928  QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
1929 
1930  oList = sCredentials.split( ':' );
1931 
1932  if (oList.count() < 2)
1933  {
1934  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid number of tokens");
1935  return false;
1936  }
1937 
1938  QString sUsername = oList[0];
1939  QString sPassword = oList[1];
1940 
1941  if (sUsername == "nouser") // Special logout username
1942  return false;
1943 
1944  MythSessionManager *sessionManager = gCoreContext->GetSessionManager();
1945  if (!MythSessionManager::IsValidUser(sUsername))
1946  {
1947  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid username");
1948  return false;
1949  }
1950 
1951  QString client = QString("WebFrontend_%1").arg(GetPeerAddress());
1952  MythUserSession session = sessionManager->LoginUser(sUsername, sPassword,
1953  client);
1954 
1955  if (!session.IsValid())
1956  {
1957  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid password");
1958  return false;
1959  }
1960 
1961  LOG(VB_HTTP, LOG_NOTICE, "Valid Authorization received");
1962 
1963  if (IsEncrypted()) // Only set a session cookie for encrypted connections, not safe otherwise
1964  SetCookie("sessionToken", session.GetSessionToken(),
1965  session.GetSessionExpires(), true);
1966 
1967  m_userSession = session;
1968 
1969  return false;
1970 }
1971 
1973 //
1975 
1977 {
1978  LOG(VB_HTTP, LOG_NOTICE, "Attempting HTTP Digest Authentication");
1979  QString realm = "MythTV"; // TODO Check which realm applies for the request path
1980 
1981  QString authMethod = GetLastHeader( "authorization" ).section(' ', 0, 0).toLower();
1982 
1983  if (authMethod != "digest")
1984  {
1985  LOG(VB_GENERAL, LOG_WARNING, "Invalid method in Authorization header");
1986  return false;
1987  }
1988 
1989  QString parameterStr = GetLastHeader( "authorization" ).section(' ', 1);
1990 
1991  QMap<QString, QString> paramMap;
1992  QStringList paramList = parameterStr.split(',');
1993  QStringList::iterator it;
1994  for (it = paramList.begin(); it != paramList.end(); ++it)
1995  {
1996  QString key = (*it).section('=', 0, 0).toLower().trimmed();
1997  // Since the value may contain '=' return everything after first occurence
1998  QString value = (*it).section('=', 1).trimmed();
1999  // Remove any quotes surrounding the value
2000  value.remove("\"");
2001  paramMap[key] = value;
2002  }
2003 
2004  if (paramMap.size() < 8)
2005  {
2006  LOG(VB_GENERAL, LOG_WARNING, "Invalid number of parameters in Authorization header");
2007  return false;
2008  }
2009 
2010  if (paramMap["nonce"].isEmpty() || paramMap["username"].isEmpty() ||
2011  paramMap["realm"].isEmpty() || paramMap["uri"].isEmpty() ||
2012  paramMap["response"].isEmpty() || paramMap["qop"].isEmpty() ||
2013  paramMap["cnonce"].isEmpty() || paramMap["nc"].isEmpty())
2014  {
2015  LOG(VB_GENERAL, LOG_WARNING, "Missing required parameters in Authorization header");
2016  return false;
2017  }
2018 
2019  if (paramMap["username"] == "nouser") // Special logout username
2020  return false;
2021 
2022  if (paramMap["uri"] != m_sOriginalUrl)
2023  {
2024  LOG(VB_GENERAL, LOG_WARNING, "Authorization URI doesn't match the "
2025  "request URI");
2026  m_nResponseStatus = 400; // Bad Request
2027  return false;
2028  }
2029 
2030  if (paramMap["realm"] != realm)
2031  {
2032  LOG(VB_GENERAL, LOG_WARNING, "Authorization realm doesn't match the "
2033  "realm of the requested content");
2034  return false;
2035  }
2036 
2037  QByteArray nonce = paramMap["nonce"].toLatin1();
2038  if (nonce.length() < 20)
2039  {
2040  LOG(VB_GENERAL, LOG_WARNING, "Authorization nonce is too short");
2041  return false;
2042  }
2043 
2044  QString nonceTimeStampStr = nonce.left(20); // ISO 8601 fixed length
2045  if (nonce != CalculateDigestNonce(nonceTimeStampStr))
2046  {
2047  LOG(VB_GENERAL, LOG_WARNING, "Authorization nonce doesn't match reference");
2048  LOG(VB_HTTP, LOG_DEBUG, QString("%1 vs %2").arg(QString(nonce))
2049  .arg(CalculateDigestNonce(nonceTimeStampStr)));
2050  return false;
2051  }
2052 
2053  const int AUTH_TIMEOUT = 2 * 60; // 2 Minute timeout to login, to reduce replay attack window
2054  QDateTime nonceTimeStamp = MythDate::fromString(nonceTimeStampStr);
2055  if (!nonceTimeStamp.isValid())
2056  {
2057  LOG(VB_GENERAL, LOG_WARNING, "Authorization nonce timestamp is invalid.");
2058  LOG(VB_HTTP, LOG_DEBUG, QString("Timestamp was '%1'").arg(nonceTimeStampStr));
2059  return false;
2060  }
2061 
2062  if (nonceTimeStamp.secsTo(MythDate::current()) > AUTH_TIMEOUT)
2063  {
2064  LOG(VB_HTTP, LOG_NOTICE, "Authorization nonce timestamp is invalid or too old.");
2065  // Tell the client that the submitted nonce has expired at which
2066  // point they may wish to try again with a fresh nonce instead of
2067  // telling the user that their credentials were invalid
2068  SetResponseHeader("WWW-Authenticate", GetAuthenticationHeader(true), true);
2069  return false;
2070  }
2071 
2072  MythSessionManager *sessionManager = gCoreContext->GetSessionManager();
2073  if (!MythSessionManager::IsValidUser(paramMap["username"]))
2074  {
2075  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid username");
2076  return false;
2077  }
2078 
2079  if (paramMap["response"].length() != 32)
2080  {
2081  LOG(VB_GENERAL, LOG_WARNING, "Authorization response field is invalid length");
2082  return false;
2083  }
2084 
2085  // If you're still reading this, well done, not far to go now
2086 
2087  QByteArray a1 = MythSessionManager::GetPasswordDigest(paramMap["username"]).toLatin1();
2088  //QByteArray a1 = "bcd911b2ecb15ffbd6d8e6e744d60cf6";
2089  QString methodDigest = QString("%1:%2").arg(GetRequestType()).arg(paramMap["uri"]);
2090  QByteArray a2 = QCryptographicHash::hash(methodDigest.toLatin1(),
2091  QCryptographicHash::Md5).toHex();
2092 
2093  QString responseDigest = QString("%1:%2:%3:%4:%5:%6").arg(QString(a1))
2094  .arg(paramMap["nonce"])
2095  .arg(paramMap["nc"])
2096  .arg(paramMap["cnonce"])
2097  .arg(paramMap["qop"])
2098  .arg(QString(a2));
2099  QByteArray kd = QCryptographicHash::hash(responseDigest.toLatin1(),
2100  QCryptographicHash::Md5).toHex();
2101 
2102  if (paramMap["response"].toLatin1() == kd)
2103  {
2104  LOG(VB_HTTP, LOG_NOTICE, "Valid Authorization received");
2105  QString client = QString("WebFrontend_%1").arg(GetPeerAddress());
2106  MythUserSession session = sessionManager->LoginUser(paramMap["username"],
2107  a1,
2108  client);
2109  if (!session.IsValid())
2110  {
2111  LOG(VB_GENERAL, LOG_ERR, "Valid Authorization received, but we "
2112  "failed to create a valid session");
2113  return false;
2114  }
2115 
2116  if (IsEncrypted()) // Only set a session cookie for encrypted connections, not safe otherwise
2117  SetCookie("sessionToken", session.GetSessionToken(),
2118  session.GetSessionExpires(), true);
2119 
2120  m_userSession = session;
2121 
2122  return true;
2123  }
2124 
2125  LOG(VB_GENERAL, LOG_WARNING, "Authorization attempt with invalid password digest");
2126  LOG(VB_HTTP, LOG_DEBUG, QString("Received hash was '%1', calculated hash was '%2'")
2127  .arg(paramMap["response"])
2128  .arg(QString(kd)));
2129 
2130  return false;
2131 }
2132 
2134 //
2136 
2138 {
2139  // Check if the existing user has permission to access this resource
2140  if (m_userSession.IsValid()) //m_userSession.CheckPermission())
2141  return true;
2142 
2143  QStringList oList = GetLastHeader( "authorization" ).split( ' ' );
2144 
2145  if (oList.count() < 2)
2146  return false;
2147 
2148  if (oList[0].compare( "basic", Qt::CaseInsensitive ) == 0)
2149  return BasicAuthentication();
2150  if (oList[0].compare( "digest", Qt::CaseInsensitive ) == 0)
2151  return DigestAuthentication();
2152 
2153  return false;
2154 }
2155 
2157 //
2159 
2160 void HTTPRequest::SetResponseHeader(const QString& sKey, const QString& sValue,
2161  bool replace)
2162 {
2163  if (!replace && m_mapRespHeaders.contains(sKey))
2164  return;
2165 
2166  m_mapRespHeaders[sKey] = sValue;
2167 }
2168 
2170 //
2172 
2173 void HTTPRequest::SetCookie(const QString &sKey, const QString &sValue,
2174  const QDateTime &expiryDate, bool secure)
2175 {
2176  if (secure && !IsEncrypted())
2177  {
2178  LOG(VB_GENERAL, LOG_WARNING, QString("HTTPRequest::SetCookie(%1=%2): "
2179  "A secure cookie cannot be set on an unencrypted connection.")
2180  .arg(sKey).arg(sValue));
2181  return;
2182  }
2183 
2184  QStringList cookieAttributes;
2185 
2186  // Key=Value
2187  cookieAttributes.append(QString("%1=%2").arg(sKey).arg(sValue));
2188 
2189  // Domain - Most browsers have problems with a hostname, so it's better to omit this
2190 // cookieAttributes.append(QString("Domain=%1").arg(GetHostName()));
2191 
2192  // Path - Fix to root, no call for restricting to other paths yet
2193  cookieAttributes.append("Path=/");
2194 
2195  // Expires - Expiry date, always set one, just good practice
2196  QString expires = MythDate::toString(expiryDate, MythDate::kRFC822); // RFC 822
2197  cookieAttributes.append(QString("Expires=%1").arg(expires)); // Cookie Expiry date
2198 
2199  // Secure - Only send this cookie over encrypted connections, it contains
2200  // sensitive info SECURITY
2201  if (secure)
2202  cookieAttributes.append("Secure");
2203 
2204  // HttpOnly - No cookie stealing javascript SECURITY
2205  cookieAttributes.append("HttpOnly");
2206 
2207  SetResponseHeader("Set-Cookie", cookieAttributes.join("; "));
2208 }
2209 
2211 //
2213 
2215 {
2216  // TODO: This only deals with the HTTP 1.1 case, 1.0 should be rare but we
2217  // should probably still handle it
2218 
2219  // RFC 3875 - The is the hostname or ip address in the client request, not
2220  // the name or ip we might otherwise know for this server
2221  QString hostname = GetLastHeader("host");
2222  if (!hostname.isEmpty())
2223  {
2224  // Strip the port
2225  if (hostname.contains("]:")) // IPv6 port
2226  {
2227  return hostname.section("]:", 0 , 0);
2228  }
2229  if (hostname.contains(":")) // IPv4 port
2230  {
2231  return hostname.section(":", 0 , 0);
2232  }
2233  return hostname;
2234  }
2235 
2236  return GetHostAddress();
2237 }
2238 
2239 
2241 {
2242  QString type;
2243  switch ( m_eType )
2244  {
2245  case RequestTypeUnknown :
2246  type = "UNKNOWN";
2247  break;
2248  case RequestTypeGet :
2249  type = "GET";
2250  break;
2251  case RequestTypeHead :
2252  type = "HEAD";
2253  break;
2254  case RequestTypePost :
2255  type = "POST";
2256  break;
2257  case RequestTypeOptions:
2258  type = "OPTIONS";
2259  break;
2260  case RequestTypeMSearch:
2261  type = "M-SEARCH";
2262  break;
2263  case RequestTypeNotify:
2264  type = "NOTIFY";
2265  break;
2266  case RequestTypeSubscribe :
2267  type = "SUBSCRIBE";
2268  break;
2269  case RequestTypeUnsubscribe :
2270  type = "UNSUBSCRIBE";
2271  break;
2272  case RequestTypeResponse :
2273  type = "RESPONSE";
2274  break;
2275  }
2276 
2277  return type;
2278 }
2279 
2280 void HTTPRequest::AddCORSHeaders( const QString &sOrigin )
2281 {
2282  // ----------------------------------------------------------------------
2283  // SECURITY: Access-Control-Allow-Origin Wildcard
2284  //
2285  // This is a REALLY bad idea, so bad in fact that I'm including it here but
2286  // commented out in the hope that anyone thinking of adding it in the future
2287  // will see it and then read this comment.
2288  //
2289  // Browsers do not verify that the origin is on the same network. This means
2290  // that a malicious script embedded or included into ANY webpage you visit
2291  // could then access servers on your local network including MythTV. They
2292  // can grab data, delete data including recordings and videos, schedule
2293  // recordings and generally ruin your day.
2294  //
2295  // This might seem paranoid and a remote possibility, but then that's how
2296  // a lot of exploits are born. Do NOT allow wildcards.
2297  //
2298  //m_mapRespHeaders[ "Access-Control-Allow-Origin" ] = "*";
2299  // ----------------------------------------------------------------------
2300 
2301  // ----------------------------------------------------------------------
2302  // SECURITY: Allow the WebFrontend on the Master backend and ONLY this
2303  // machine to access resources on a frontend or slave web server
2304  //
2305  // http://www.w3.org/TR/cors/#introduction
2306  // ----------------------------------------------------------------------
2307 
2308  QStringList allowedOrigins;
2309 
2310  int serverStatusPort = gCoreContext->GetMasterServerStatusPort();
2311  int backendSSLPort = gCoreContext->GetNumSetting( "BackendSSLPort",
2312  serverStatusPort + 10);
2313 
2314  QString masterAddrPort = QString("%1:%2")
2316  .arg(serverStatusPort);
2317  QString masterTLSAddrPort = QString("%1:%2")
2319  .arg(backendSSLPort);
2320 
2321  allowedOrigins << QString("http://%1").arg(masterAddrPort);
2322  allowedOrigins << QString("https://%2").arg(masterTLSAddrPort);
2323 
2324  QString localhostname = QHostInfo::localHostName();
2325  if (!localhostname.isEmpty())
2326  {
2327  allowedOrigins << QString("http://%1:%2")
2328  .arg(localhostname).arg(serverStatusPort);
2329  allowedOrigins << QString("https://%1:%2")
2330  .arg(localhostname).arg(backendSSLPort);
2331  }
2332 
2333  QStringList allowedOriginsList =
2334  gCoreContext->GetSetting("AllowedOriginsList", QString(
2335  "https://chromecast.mythtv.org,"
2336  "http://chromecast.mythtvcast.com"
2337  )).split(",");
2338 
2339  for (const auto & origin : qAsConst(allowedOriginsList))
2340  {
2341  if (origin.isEmpty())
2342  continue;
2343 
2344  if (origin == "*" || (!origin.startsWith("http://") &&
2345  !origin.startsWith("https://")))
2346  {
2347  LOG(VB_GENERAL, LOG_ERR, QString("Illegal AllowedOriginsList"
2348  " entry '%1'. Must start with http[s]:// and not be *")
2349  .arg(origin));
2350  }
2351  else
2352  {
2353  allowedOrigins << origin;
2354  }
2355  }
2356 
2357  if (VERBOSE_LEVEL_CHECK(VB_HTTP, LOG_DEBUG))
2358  {
2359  for (const auto & origin : qAsConst(allowedOrigins))
2360  LOG(VB_HTTP, LOG_DEBUG, QString("Will allow Origin: %1").arg(origin));
2361  }
2362 
2363  if (allowedOrigins.contains(sOrigin))
2364  {
2365  SetResponseHeader( "Access-Control-Allow-Origin" , sOrigin);
2366  SetResponseHeader( "Access-Control-Allow-Credentials" , "true");
2367  SetResponseHeader( "Access-Control-Allow-Headers" , "Content-Type");
2368  LOG(VB_HTTP, LOG_DEBUG, QString("Allow-Origin: %1)").arg(sOrigin));
2369  }
2370  else
2371  {
2372  LOG(VB_GENERAL, LOG_CRIT, QString("HTTPRequest: Cross-origin request "
2373  "received with origin (%1)")
2374  .arg(sOrigin));
2375  }
2376 }
2377 
2380 //
2381 // BufferedSocketDeviceRequest Class Implementation
2382 //
2385 
2387 {
2388  QString sLine;
2389 
2390  if (m_pSocket && m_pSocket->isValid() &&
2391  m_pSocket->state() == QAbstractSocket::ConnectedState)
2392  {
2393  bool timeout = false;
2394  MythTimer timer;
2395  timer.start();
2396  while (!m_pSocket->canReadLine() && !timeout)
2397  {
2398  timeout = !(m_pSocket->waitForReadyRead( msecs ));
2399 
2400  if ( timer.elapsed() >= msecs )
2401  {
2402  timeout = true;
2403  LOG(VB_HTTP, LOG_INFO, "BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2404  }
2405  }
2406 
2407  if (!timeout)
2408  sLine = m_pSocket->readLine();
2409  }
2410 
2411  return( sLine );
2412 }
2413 
2415 //
2417 
2418 qint64 BufferedSocketDeviceRequest::ReadBlock(char *pData, qint64 nMaxLen,
2419  int msecs)
2420 {
2421  if (m_pSocket && m_pSocket->isValid() &&
2422  m_pSocket->state() == QAbstractSocket::ConnectedState)
2423  {
2424  if (msecs == 0)
2425  return( m_pSocket->read( pData, nMaxLen ));
2426 
2427  bool bTimeout = false;
2428  MythTimer timer;
2429  timer.start();
2430  while ( (m_pSocket->bytesAvailable() < (int)nMaxLen) && !bTimeout ) // This can end up waiting far longer than msecs
2431  {
2432  bTimeout = !(m_pSocket->waitForReadyRead( msecs ));
2433 
2434  if ( timer.elapsed() >= msecs )
2435  {
2436  bTimeout = true;
2437  LOG(VB_HTTP, LOG_INFO, "BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2438  }
2439  }
2440 
2441  // Just return what we have even if timed out.
2442 
2443  return( m_pSocket->read( pData, nMaxLen ));
2444  }
2445 
2446  return( -1 );
2447 }
2448 
2450 //
2452 
2453 qint64 BufferedSocketDeviceRequest::WriteBlock(const char *pData, qint64 nLen)
2454 {
2455  qint64 bytesWritten = -1;
2456  if (m_pSocket && m_pSocket->isValid() &&
2457  m_pSocket->state() == QAbstractSocket::ConnectedState)
2458  {
2459  bytesWritten = m_pSocket->write( pData, nLen );
2460  m_pSocket->waitForBytesWritten();
2461  }
2462 
2463  return( bytesWritten );
2464 }
2465 
2467 //
2469 
2471 {
2472  return( m_pSocket->localAddress().toString() );
2473 }
2474 
2476 //
2478 
2480 {
2481  return( m_pSocket->localPort() );
2482 }
2483 
2484 
2486 //
2488 
2490 {
2491  return( m_pSocket->peerAddress().toString() );
2492 }
2493 
jsonSerializer.h
HTTPRequest::GetRequestHeader
QString GetRequestHeader(const QString &sKey, const QString &sDefault)
Definition: httprequest.cpp:1176
MythSessionManager::IsValidUser
static bool IsValidUser(const QString &username)
Check if the given user exists but not whether there is a valid session open for them!
Definition: mythsession.cpp:151
ContentType_XML
@ ContentType_XML
Definition: httprequest.h:69
build_compdb.args
args
Definition: build_compdb.py:11
HTTPRequest::m_sBaseUrl
QString m_sBaseUrl
Definition: httprequest.h:125
e
QDomElement e
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1420
MythSessionManager::GetPasswordDigest
static QString GetPasswordDigest(const QString &username)
Load the password digest for comparison in the HTTP Auth code.
Definition: mythsession.cpp:224
HTTPRequest::SetContentType
HttpContentType SetContentType(const QString &sType)
Definition: httprequest.cpp: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:79
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:1262
doc
QDomDocument doc("MYTHARCHIVEITEM")
BufferedSocketDeviceRequest::GetHostPort
quint16 GetHostPort() override
Definition: httprequest.cpp:2479
XmlPListSerializer
Definition: xmlplistSerializer.h:12
BufferedSocketDeviceRequest::GetPeerAddress
QString GetPeerAddress() override
Definition: httprequest.cpp:2489
HTTPRequest::m_sMethod
QString m_sMethod
Definition: httprequest.h:127
HTTPRequest::m_sResourceUrl
QString m_sResourceUrl
Definition: httprequest.h:126
BufferedSocketDeviceRequest::GetHostAddress
QString GetHostAddress() override
Definition: httprequest.cpp:2470
ResponseTypeUnknown
@ ResponseTypeUnknown
Definition: httprequest.h:75
JSONSerializer
Definition: jsonSerializer.h:31
HTTPRequest::IsEncrypted
bool IsEncrypted() const
Definition: httprequest.h:196
HTTPRequest::m_sProtocol
QString m_sProtocol
Definition: httprequest.h:135
mythcoreutil.h
HTTPRequest::m_sResponseTypeText
QString m_sResponseTypeText
Definition: httprequest.h:148
HTTPRequest::GetResponseProtocol
static QString GetResponseProtocol()
Definition: httprequest.cpp:923
HTTPRequest::m_sOriginalUrl
QString m_sOriginalUrl
Definition: httprequest.h:123
HTTPRequest::GetPeerAddress
virtual QString GetPeerAddress()=0
HTTPRequest::GetSupportedMimeTypes
static QStringList GetSupportedMimeTypes()
Definition: httprequest.cpp:1064
arg
arg(title).arg(filename).arg(doDelete))
MythDate::kOverrideUTC
@ kOverrideUTC
Present date/time in UTC.
Definition: mythdate.h:28
ResponseTypeJS
@ ResponseTypeJS
Definition: httprequest.h:78
ResponseTypeNone
@ ResponseTypeNone
Definition: httprequest.h:74
RequestTypePost
@ RequestTypePost
Definition: httprequest.h:49
HTTPRequest::Authenticated
bool Authenticated()
Definition: httprequest.cpp:2137
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
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
RequestTypeResponse
@ RequestTypeResponse
Definition: httprequest.h:61
HTTPRequest::DigestAuthentication
bool DigestAuthentication()
Definition: httprequest.cpp:1976
HTTPRequest::m_userSession
MythUserSession m_userSession
Definition: httprequest.h:160
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:1917
HTTPRequest::m_bProtected
bool m_bProtected
Definition: httprequest.h:139
RequestTypeSubscribe
@ RequestTypeSubscribe
Definition: httprequest.h:57
HTTPRequest::TestMimeType
static QString TestMimeType(const QString &sFileName)
Definition: httprequest.cpp:1081
HTTPRequest::Encode
static QString Encode(const QString &sIn)
Definition: httprequest.cpp:1805
HTTPRequest::SetCookie
void SetCookie(const QString &sKey, const QString &sValue, const QDateTime &expiryDate, bool secure)
Definition: httprequest.cpp:2173
RequestTypeUnsubscribe
@ RequestTypeUnsubscribe
Definition: httprequest.h:58
HTTPRequest::m_procReqLineExp
QRegularExpression m_procReqLineExp
Definition: httprequest.h:113
xmlSerializer.h
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
HTTPRequest::Decode
static QString Decode(const QString &sIn)
Definition: httprequest.cpp:1829
HTTPRequest::GetSerializer
Serializer * GetSerializer()
Definition: httprequest.cpp:1768
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:1014
SoapSerializer
Definition: soapSerializer.h:27
HTTPRequest::GetResponseHeaders
QString GetResponseHeaders(void)
Definition: httprequest.cpp:1191
HTTPRequest::ExtractMethodFromURL
void ExtractMethodFromURL()
Definition: httprequest.cpp:1629
HTTPRequest::m_sPrivateToken
QString m_sPrivateToken
Definition: httprequest.h:159
mythversion.h
HTTPRequest::m_nMajor
int m_nMajor
Definition: httprequest.h:136
ResponseTypeFile
@ ResponseTypeFile
Definition: httprequest.h:82
MythUserSession
Definition: mythsession.h:16
ResponseTypeHeader
@ ResponseTypeHeader
Definition: httprequest.h:84
HTTPRequest::m_mapRespHeaders
QStringMap m_mapRespHeaders
Definition: httprequest.h:151
HTTPRequest::WriteBlock
virtual qint64 WriteBlock(const char *pData, qint64 nLen)=0
NameValues
Definition: upnputil.h:89
HTTPRequest::FormatActionResponse
void FormatActionResponse(Serializer *ser)
Definition: httprequest.cpp: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:111
upnp.h
HTTPRequest::m_nResponseStatus
long m_nResponseStatus
Definition: httprequest.h:150
mythlogging.h
HttpContentType
HttpContentType
Definition: httprequest.h:65
RequestTypeMSearch
@ RequestTypeMSearch
Definition: httprequest.h:56
HTTPRequest::GetResponsePage
QByteArray GetResponsePage(void)
Definition: httprequest.cpp:1016
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:987
HTTPRequest::GetMimeType
static QString GetMimeType(const QString &sFileExtension)
Definition: httprequest.cpp:1045
MythUserSession::IsValid
bool IsValid(void) const
Check if this session object appears properly constructed, it DOES NOT validate whether it is a valid...
Definition: mythsession.cpp:15
HTTPRequest::m_mapParams
QStringMap m_mapParams
Definition: httprequest.h:129
HTTPRequest::m_parseRangeExp
QRegularExpression m_parseRangeExp
Definition: httprequest.h:114
compat.h
QStringMap
QMap< QString, QString > QStringMap
Definition: upnputil.h:44
RequestTypeHead
@ RequestTypeHead
Definition: httprequest.h:48
HTTPRequest::BuildResponseHeader
QString BuildResponseHeader(long long nSize)
Definition: httprequest.cpp:204
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
Serializer::AddHeaders
virtual void AddHeaders(QStringMap &headers)
Definition: serializer.cpp:22
HTTPRequest::m_bKeepAlive
bool m_bKeepAlive
Definition: httprequest.h:164
HTTPRequest::m_sRequestUrl
QString m_sRequestUrl
Definition: httprequest.h:124
ResponseTypeXML
@ ResponseTypeXML
Definition: httprequest.h:76
MythUserSession::GetSessionToken
QString GetSessionToken(void) const
Definition: mythsession.h:38
HTTPRequest::ParseRange
bool ParseRange(QString sRange, long long llSize, long long *pllStart, long long *pllEnd)
Definition: httprequest.cpp:1525
HTTPRequest::GetETagHash
static QString GetETagHash(const QByteArray &data)
Definition: httprequest.cpp:1845
HTTPRequest::m_sRawRequest
QString m_sRawRequest
Definition: httprequest.h:121
HTTPRequest::ProcessRequestLine
void ProcessRequestLine(const QString &sLine)
Definition: httprequest.cpp:1454
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:130
ResponseTypeOther
@ ResponseTypeOther
Definition: httprequest.h:83
HTTPRequest::GetHostName
virtual QString GetHostName()
Definition: httprequest.cpp:2214
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:933
HTTPRequest::SendData
qint64 SendData(QIODevice *pDevice, qint64 llStart, qint64 llBytes)
Definition: httprequest.cpp:671
HTTPRequest::m_sFileName
QString m_sFileName
Definition: httprequest.h:153
HTTPRequest::GetResponseStatus
QString GetResponseStatus(void) const
Definition: httprequest.cpp:962
ResponseTypeHTML
@ ResponseTypeHTML
Definition: httprequest.h:77
MythSessionManager::LoginUser
MythUserSession LoginUser(const QString &username, const QByteArray &digest, const QString &client="")
Login user by digest.
Definition: mythsession.cpp:282
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
HTTPRequest::ParseCookies
void ParseCookies(void)
Definition: httprequest.cpp:1244
HTTPRequest::m_bSOAPRequest
bool m_bSOAPRequest
Definition: httprequest.h:142
MythTimer::elapsed
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
HTTPRequest::CalculateDigestNonce
QString CalculateDigestNonce(const QString &timeStamp) const
Definition: httprequest.cpp:1905
ResponseTypeText
@ ResponseTypeText
Definition: httprequest.h:80
RequestTypeNotify
@ RequestTypeNotify
Definition: httprequest.h:59
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
BufferedSocketDeviceRequest::WriteBlock
qint64 WriteBlock(const char *pData, qint64 nLen) override
Definition: httprequest.cpp:2453
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:2160
MythDate::kRFC822
@ kRFC822
HTTP Date format.
Definition: mythdate.h:27
HTTPRequest::m_eContentType
HttpContentType m_eContentType
Definition: httprequest.h:119
HTTPRequest::SendFile
qint64 SendFile(QFile &file, qint64 llStart, qint64 llBytes)
Definition: httprequest.cpp:721
MythCoreContext::GetLanguageAndVariant
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
Definition: mythcorecontext.cpp:1794
HTTPRequest::getSocketHandle
virtual int getSocketHandle()=0
HTTPRequest::m_eResponseType
HttpResponseType m_eResponseType
Definition: httprequest.h:147
HTTPRequest::m_nMinor
int m_nMinor
Definition: httprequest.h:137
HTTPRequest::m_sPayload
QString m_sPayload
Definition: httprequest.h:133
HTTPRequest::GetRequestProtocol
QString GetRequestProtocol() const
Definition: httprequest.cpp: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:1132
MythDate::current_iso_string
QString current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
Definition: mythdate.cpp:18
HTTPRequest::m_sNameSpace
QString m_sNameSpace
Definition: httprequest.h:143
UPnp::GetConfiguration
static Configuration * GetConfiguration()
Definition: upnp.cpp:84
mythtimer.h
HTTPRequest::AddCORSHeaders
void AddCORSHeaders(const QString &sOrigin)
Definition: httprequest.cpp:2280
RequestTypeUnknown
@ RequestTypeUnknown
Definition: httprequest.h:45
BufferedSocketDeviceRequest::m_pSocket
QTcpSocket * m_pSocket
Definition: httprequest.h:280
HTTPRequest::GetResponseType
QString GetResponseType(void) const
Definition: httprequest.cpp:1025
musicbrainzngs.caa.hostname
string hostname
Definition: caa.py:17
HTTPRequest::m_nKeepAliveTimeout
uint m_nKeepAliveTimeout
Definition: httprequest.h:165
HTTPRequest::SendResponse
qint64 SendResponse(void)
Definition: httprequest.cpp:312
HTTPRequest::ProcessSOAPPayload
bool ProcessSOAPPayload(const QString &sSOAPAction)
Definition: httprequest.cpp:1660
HTTPRequest::GetAuthenticationHeader
QString GetAuthenticationHeader(bool isStale=false)
Definition: httprequest.cpp:1875
MythCoreContext::GetSessionManager
MythSessionManager * GetSessionManager(void)
Definition: mythcorecontext.cpp:2052
xmlplistSerializer.h
HTTPRequest::IsUrlProtected
static bool IsUrlProtected(const QString &sBaseUrl)
Definition: httprequest.cpp:1856
ResponseTypeSVG
@ ResponseTypeSVG
Definition: httprequest.h:81
HTTPRequest::GetHostAddress
virtual QString GetHostAddress()=0
HTTPRequest::ReadBlock
virtual qint64 ReadBlock(char *pData, qint64 nMaxLen, int msecs=0)=0
RequestTypeGet
@ RequestTypeGet
Definition: httprequest.h:47
httprequest.h
BufferedSocketDeviceRequest::ReadLine
QString ReadLine(int msecs) override
Definition: httprequest.cpp:2386
HTTPRequest::m_response
QBuffer m_response
Definition: httprequest.h:155
HTTPRequest::ReadLine
virtual QString ReadLine(int msecs)=0
HTTPRequest::ParseKeepAlive
bool ParseKeepAlive(void)
Definition: httprequest.cpp:1210
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:2240
soapSerializer.h
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:919
BufferedSocketDeviceRequest::ReadBlock
qint64 ReadBlock(char *pData, qint64 nMaxLen, int msecs=0) override
Definition: httprequest.cpp:2418
MythSessionManager
Definition: mythsession.h:98
HTTPRequest::m_eType
HttpRequestType m_eType
Definition: httprequest.h:118
HTTPRequest::m_mapCookies
QStringMap m_mapCookies
Definition: httprequest.h:131