MythTV  master
htmlserver.cpp
Go to the documentation of this file.
1 // Program Name: htmlserver.cpp
3 // Created : Mar. 9, 2011
4 //
5 // Purpose : Http server extension to serve up static html content
6 //
7 // Copyright (c) 2011 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see COPYING for details
10 //
12 
13 #include "mythlogging.h"
14 #include "htmlserver.h"
15 #include "storagegroup.h"
16 #include "httprequest.h"
17 
19 
20 #include <QFileInfo>
21 #include <QDir>
22 #include <QTextStream>
23 #include <QUuid>
24 
26 //
28 
29 HtmlServerExtension::HtmlServerExtension( const QString &sSharePath,
30  const QString &sApplicationPrefix)
31  : HttpServerExtension( "Html" , sSharePath),
32  m_IndexFilename(sApplicationPrefix + "index")
33 {
34  LOG(VB_HTTP, LOG_INFO, QString("HtmlServerExtension() - SharePath = %1")
35  .arg(m_sSharePath));
37 
38  // ----------------------------------------------------------------------
39  // Register Rtti with QtScript Engine.
40  // Rtti exposes internal enums complete with translations for use in scripts
41  // ----------------------------------------------------------------------
42 
43  QScriptEngine *pEngine = ScriptEngine();
44  pEngine->globalObject().setProperty("Rtti",
45  pEngine->scriptValueFromQMetaObject< ScriptableRtti >() );
46 }
47 
49 //
51 
53 {
54  if (pRequest)
55  {
56  if ( !pRequest->m_sBaseUrl.startsWith("/"))
57  return( false );
58 
59  if ((pRequest->m_eType != RequestTypeGet) &&
60  (pRequest->m_eType != RequestTypeHead) &&
61  (pRequest->m_eType != RequestTypePost))
62  {
64  pRequest->m_nResponseStatus = 405; // Method not allowed
65  // Conservative list, we can't really know what methods we
66  // actually allow for an arbitrary resource without some sort of
67  // high maintenance database
68  pRequest->m_response.write( pRequest->GetResponsePage() );
69  pRequest->SetResponseHeader("Allow", "GET, HEAD");
70  return true;
71  }
72 
73  bool bStorageGroupFile = false;
74  QFileInfo oInfo( m_sSharePath + pRequest->m_sResourceUrl );
75 
76  if (oInfo.isDir())
77  {
78  QString sIndexFileName = oInfo.filePath() + m_IndexFilename + ".qsp";
79 
80  if (QFile::exists( sIndexFileName ))
81  oInfo.setFile( sIndexFileName );
82  else
83  oInfo.setFile( oInfo.filePath() + m_IndexFilename + ".html" );
84  }
85 
86  if (pRequest->m_sResourceUrl.startsWith("/StorageGroup/"))
87  {
88  StorageGroup oGroup(pRequest->m_sResourceUrl.section('/', 2, 2));
89  QString sFile =
90  oGroup.FindFile(pRequest->m_sResourceUrl.section('/', 3));
91  if (!sFile.isEmpty())
92  {
93  oInfo.setFile(sFile);
94  bStorageGroupFile = true;
95  }
96  }
97 
98  if (bStorageGroupFile || oInfo.exists() )
99  {
100  QString sResName = oInfo.canonicalFilePath();
101 
102  // --------------------------------------------------------------
103  // Checking for url's that contain ../ or similar.
104  // --------------------------------------------------------------
105 
106  if (( bStorageGroupFile ) ||
107  (sResName.startsWith( m_sSharePath, Qt::CaseInsensitive )))
108  {
109  if (oInfo.exists())
110  {
111  if (oInfo.isSymLink())
112  sResName = oInfo.symLinkTarget();
113 
114  // ------------------------------------------------------
115  // CSP Nonce
116  // ------------------------------------------------------
117  QByteArray cspNonce = QUuid::createUuid().toByteArray().toBase64();
118  cspNonce = cspNonce.mid(1, cspNonce.length() - 2); // UUID, with braces removed
119 
120  // ------------------------------------------------------
121  // Is this a Qt Server Page (File contains script)...
122  // ------------------------------------------------------
123 
124  QString sSuffix = oInfo.suffix().toLower();
125 
126  QString sMimeType = HTTPRequest::GetMimeType(sSuffix);
127 
128  if (sMimeType == "text/html")
129  pRequest->m_eResponseType = ResponseTypeHTML;
130  else if (sMimeType == "text/xml")
131  pRequest->m_eResponseType = ResponseTypeXML;
132  else if (sMimeType == "application/javascript")
133  pRequest->m_eResponseType = ResponseTypeJS;
134  else if (sMimeType == "text/css")
135  pRequest->m_eResponseType = ResponseTypeCSS;
136  else if (sMimeType == "text/plain")
137  pRequest->m_eResponseType = ResponseTypeText;
138  else if (sMimeType == "image/svg+xml" &&
139  sSuffix != "svgz") // svgz are pre-compressed
140  pRequest->m_eResponseType = ResponseTypeSVG;
141 
142  // ---------------------------------------------------------
143  // Force IE into 'standards' mode
144  // ---------------------------------------------------------
145  pRequest->SetResponseHeader("X-UA-Compatible", "IE=Edge");
146 
147  // ---------------------------------------------------------
148  // SECURITY: Set X-Content-Type-Options to 'nosniff'
149  //
150  // IE only for now. Prevents browsers ignoring the
151  // Content-Type header we supply and potentially executing
152  // malicious script embedded in an image or css file.
153  //
154  // Yes, really, you need to explicitly disable this sort of
155  // dangerous behaviour in 2015!
156  // ---------------------------------------------------------
157  pRequest->SetResponseHeader("X-Content-Type-Options",
158  "nosniff");
159 
160  // ---------------------------------------------------------
161  // SECURITY: Set Content Security Policy
162  //
163  // *No external content allowed*
164  //
165  // This is an important safeguard. Third party content
166  // should never be permitted. It compromises security,
167  // privacy and violates the key principal that the
168  // WebFrontend should work on an isolated network with no
169  // internet access. Keep all content hosted locally!
170  // ---------------------------------------------------------
171 
172  // For now the following are disabled as we use xhr to
173  // trigger playback on frontends if we switch to triggering
174  // that through an internal request then these would be
175  // better enabled
176  //"default-src 'self'; "
177  //"connect-src 'self' https://services.mythtv.org; "
178 
179  // FIXME: unsafe-inline should be phased out, replaced by nonce-{csp_nonce} but it requires
180  // all inline event handlers and style attributes to be removed ...
181  QString cspPolicy = "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://services.mythtv.org; " // QString('nonce-%1').arg(QString(cspNonce))
182  "style-src 'self' 'unsafe-inline'; "
183  "frame-src 'self'; "
184  "object-src 'self'; " // TODO: When we no longer require flash for some browsers, change this to 'none'
185  "media-src 'self'; "
186  "font-src 'self'; "
187  "img-src 'self'; "
188  "form-action 'self'; "
189  "frame-ancestors 'self'; ";
190 
191  pRequest->SetResponseHeader("X-XSS-Protection", "1; mode=block");
192 
193  // For standards compliant browsers
194  pRequest->SetResponseHeader("Content-Security-Policy",
195  cspPolicy);
196  // For Internet Explorer
197  pRequest->SetResponseHeader("X-Content-Security-Policy",
198  cspPolicy);
199 
200  if ((sSuffix == "qsp") ||
201  (sSuffix == "qxml") ||
202  (sSuffix == "qjs" ))
203  {
204  QTextStream stream( &pRequest->m_response );
205 
206  m_Scripting.EvaluatePage( &stream, sResName, pRequest, cspNonce);
207 
208  return true;
209  }
210 
211  // ------------------------------------------------------
212  // Return the file.
213  // ------------------------------------------------------
214 
215  pRequest->FormatFileResponse( sResName );
216 
217  return true;
218  }
219  }
220  }
221 
222  // force return as a 404...
223  pRequest->FormatFileResponse( "" );
224  }
225 
226  return( true );
227 }
228 
QString m_IndexFilename
Definition: htmlserver.h:32
QString m_sBaseUrl
Definition: httprequest.h:123
QString m_sSharePath
Definition: httpserver.h:78
ServerSideScripting m_Scripting
Definition: htmlserver.h:31
long m_nResponseStatus
Definition: httprequest.h:148
static QString GetMimeType(const QString &sFileExtension)
QString m_sResourceUrl
Definition: httprequest.h:124
QByteArray GetResponsePage(void)
HtmlServerExtension(const QString &sSharePath, const QString &sApplicationPrefix)
Definition: htmlserver.cpp:29
void SetResponseHeader(const QString &sKey, const QString &sValue, bool replace=false)
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: htmlserver.cpp:52
bool EvaluatePage(QTextStream *pOutStream, const QString &sFileName, HTTPRequest *pRequest, const QByteArray &cspToken)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QString FindFile(const QString &filename)
QScriptEngine * ScriptEngine()
Definition: htmlserver.h:47
QBuffer m_response
Definition: httprequest.h:153
HttpResponseType m_eResponseType
Definition: httprequest.h:145
void FormatFileResponse(const QString &sFileName)
QString SetResourceRootPath(const QString &path)
HttpRequestType m_eType
Definition: httprequest.h:116