SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
staticfilecontroller.cpp
Go to the documentation of this file.
1 
6 #include "staticfilecontroller.h"
7 #include "httpdocrootsettings.h"
8 
9 #include <QFileInfo>
10 #include <QDir>
11 #include <QDateTime>
12 
13 using namespace qtwebapp;
14 
15 StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
16  :HttpRequestHandler(parent), useQtSettings(true)
17 {
18  maxAge=settings->value("maxAge","60000").toInt();
19  encoding=settings->value("encoding","UTF-8").toString();
20  docroot=settings->value("path",".").toString();
21  if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://")))
22  {
23  // Convert relative path to absolute, based on the directory of the config file.
24  #ifdef Q_OS_WIN32
25  if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
26  #else
27  if (QDir::isRelativePath(docroot))
28  #endif
29  {
30  QFileInfo configFile(settings->fileName());
31  docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath();
32  }
33  }
34  qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
35  maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
36  cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
37  cacheTimeout=settings->value("cacheTime","60000").toInt();
38  qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,cache.maxCost());
39 }
40 
42  :HttpRequestHandler(parent), useQtSettings(false)
43 {
44  maxAge=settings.maxAge;
45  encoding=settings.encoding;
46  docroot=settings.path;
47  if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://")))
48  {
49  // Convert relative path to absolute, based on the directory of the config file.
50  if (QDir::isRelativePath(docroot))
51  {
52  docroot = QFileInfo(QDir::currentPath(), docroot).absoluteFilePath();
53  }
54  }
55  qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
57  cache.setMaxCost(settings.cacheSize);
58  cacheTimeout=settings.cacheTime;
59  qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,cache.maxCost());
60 }
61 
63 {
64  QByteArray path = request.getPath();
65  service(path, response);
66 }
67 
68 void StaticFileController::service(QByteArray& path, HttpResponse& response)
69 {
70  //QByteArray path=request.getPath();
71  // Check if we have the file in cache
72  qint64 now=QDateTime::currentMSecsSinceEpoch();
73  mutex.lock();
74  CacheEntry* entry=cache.object(path);
75  if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout))
76  {
77  QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock.
78  QByteArray filename=entry->filename;
79  mutex.unlock();
80  qDebug("StaticFileController: Cache hit for %s",path.data());
81  setContentType(filename,response);
82  response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
83  response.write(document);
84  }
85  else
86  {
87  mutex.unlock();
88  // The file is not in cache.
89  qDebug("StaticFileController: Cache miss for %s",path.data());
90  // Forbid access to files outside the docroot directory
91  if (path.contains("/.."))
92  {
93  qWarning("StaticFileController::service: detected forbidden characters in path %s",path.data());
94  response.setStatus(403,"forbidden");
95  response.write("403 forbidden",true);
96  return;
97  }
98  // If the filename is a directory, append index.html.
99  if (QFileInfo(docroot+path).isDir())
100  {
101  path+="/index.html";
102  }
103  // Try to open the file
104  QFile file(docroot+path);
105  qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
106  if (file.open(QIODevice::ReadOnly))
107  {
108  setContentType(path,response);
109  response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
110  if (file.size()<=maxCachedFileSize)
111  {
112  // Return the file content and store it also in the cache
113  entry=new CacheEntry();
114  while (!file.atEnd() && !file.error())
115  {
116  QByteArray buffer=file.read(65536);
117  response.write(buffer);
118  entry->document.append(buffer);
119  }
120  entry->created=now;
121  entry->filename=path;
122  mutex.lock();
123  //cache.insert(request.getPath(),entry,entry->document.size());
124  cache.insert(path,entry,entry->document.size());
125  mutex.unlock();
126  }
127  else
128  {
129  // Return the file content, do not store in cache
130  while (!file.atEnd() && !file.error())
131  {
132  response.write(file.read(65536));
133  }
134  }
135  file.close();
136  }
137  else {
138  if (file.exists())
139  {
140  qWarning("StaticFileController::service: Cannot open existing file %s for reading",qPrintable(file.fileName()));
141  response.setStatus(403,"forbidden");
142  response.write("403 forbidden",true);
143  }
144  else
145  {
146  qWarning("StaticFileController::service: File %s not found",qPrintable(file.fileName()));
147  response.setStatus(404,"not found");
148  response.write("404 not found",true);
149  }
150  }
151  }
152 }
153 
154 void StaticFileController::setContentType(QString fileName, HttpResponse& response) const
155 {
156  if (fileName.endsWith(".png"))
157  {
158  response.setHeader("Content-Type", "image/png");
159  }
160  else if (fileName.endsWith(".jpg"))
161  {
162  response.setHeader("Content-Type", "image/jpeg");
163  }
164  else if (fileName.endsWith(".gif"))
165  {
166  response.setHeader("Content-Type", "image/gif");
167  }
168  else if (fileName.endsWith(".pdf"))
169  {
170  response.setHeader("Content-Type", "application/pdf");
171  }
172  else if (fileName.endsWith(".txt"))
173  {
174  response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
175  }
176  else if (fileName.endsWith(".html") || fileName.endsWith(".htm"))
177  {
178  response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding));
179  }
180  else if (fileName.endsWith(".css"))
181  {
182  response.setHeader("Content-Type", "text/css");
183  }
184  else if (fileName.endsWith(".js"))
185  {
186  response.setHeader("Content-Type", "text/javascript");
187  }
188  else if (fileName.endsWith(".svg"))
189  {
190  response.setHeader("Content-Type", "image/svg+xml");
191  }
192  else if (fileName.endsWith(".woff"))
193  {
194  response.setHeader("Content-Type", "font/woff");
195  }
196  else if (fileName.endsWith(".woff2"))
197  {
198  response.setHeader("Content-Type", "font/woff2");
199  }
200  else if (fileName.endsWith(".ttf"))
201  {
202  response.setHeader("Content-Type", "application/x-font-ttf");
203  }
204  else if (fileName.endsWith(".eot"))
205  {
206  response.setHeader("Content-Type", "application/vnd.ms-fontobject");
207  }
208  else if (fileName.endsWith(".otf"))
209  {
210  response.setHeader("Content-Type", "application/font-otf");
211  }
212  else if (fileName.endsWith(".yaml"))
213  {
214  response.setHeader("Content-Type", "text/plain");
215  }
216  // Todo: add all of your content types
217  else
218  {
219  qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName));
220  }
221 }
QCache< QString, CacheEntry > cache
QByteArray getPath() const
StaticFileController(QSettings *settings, QObject *parent=NULL)
void setHeader(QByteArray name, QByteArray value)
void setContentType(QString file, HttpResponse &response) const
void write(QByteArray data, bool lastPart=false)
void setStatus(int statusCode, QByteArray description=QByteArray())
void service(HttpRequest &request, HttpResponse &response)