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