staticfilecontroller.cpp 6.8 KB

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