| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | /**
 | 
					
						
							|  |  |  |   @file | 
					
						
							|  |  |  |   @author Stefan Frings | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "staticfilecontroller.h"
 | 
					
						
							| 
									
										
										
										
											2017-11-13 01:01:15 +01:00
										 |  |  | #include "httpdocrootsettings.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | #include <QFileInfo>
 | 
					
						
							|  |  |  | #include <QDir>
 | 
					
						
							|  |  |  | #include <QDateTime>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-11 09:32:15 +01:00
										 |  |  | using namespace qtwebapp; | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | StaticFileController::StaticFileController(QSettings* settings, QObject* parent) | 
					
						
							| 
									
										
										
										
											2017-11-13 01:01:15 +01:00
										 |  |  |     :HttpRequestHandler(parent), useQtSettings(true) | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     maxAge=settings->value("maxAge","60000").toInt(); | 
					
						
							|  |  |  |     encoding=settings->value("encoding","UTF-8").toString(); | 
					
						
							|  |  |  |     docroot=settings->value("path",".").toString(); | 
					
						
							|  |  |  |     if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://"))) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Convert relative path to absolute, based on the directory of the config file.
 | 
					
						
							|  |  |  |         #ifdef Q_OS_WIN32
 | 
					
						
							|  |  |  |             if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat) | 
					
						
							|  |  |  |         #else
 | 
					
						
							|  |  |  |             if (QDir::isRelativePath(docroot)) | 
					
						
							|  |  |  |         #endif
 | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             QFileInfo configFile(settings->fileName()); | 
					
						
							|  |  |  |             docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge); | 
					
						
							|  |  |  |     maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt(); | 
					
						
							|  |  |  |     cache.setMaxCost(settings->value("cacheSize","1000000").toInt()); | 
					
						
							|  |  |  |     cacheTimeout=settings->value("cacheTime","60000").toInt(); | 
					
						
							| 
									
										
										
										
											2024-09-03 18:38:32 +03:00
										 |  |  |     qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,(int)cache.maxCost()); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-13 13:46:02 +01:00
										 |  |  | StaticFileController::StaticFileController(const HttpDocrootSettings& settings, QObject* parent) | 
					
						
							| 
									
										
										
										
											2017-11-13 01:01:15 +01:00
										 |  |  |     :HttpRequestHandler(parent), useQtSettings(false) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-11-13 13:46:02 +01:00
										 |  |  |     maxAge=settings.maxAge; | 
					
						
							|  |  |  |     encoding=settings.encoding; | 
					
						
							|  |  |  |     docroot=settings.path; | 
					
						
							| 
									
										
										
										
											2017-11-13 01:01:15 +01:00
										 |  |  |     if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://"))) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Convert relative path to absolute, based on the directory of the config file.
 | 
					
						
							|  |  |  |         if (QDir::isRelativePath(docroot)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             docroot = QFileInfo(QDir::currentPath(), docroot).absoluteFilePath(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge); | 
					
						
							| 
									
										
										
										
											2017-11-13 13:46:02 +01:00
										 |  |  |     maxCachedFileSize=settings.maxCachedFileSize; | 
					
						
							|  |  |  |     cache.setMaxCost(settings.cacheSize); | 
					
						
							|  |  |  |     cacheTimeout=settings.cacheTime; | 
					
						
							| 
									
										
										
										
											2024-09-03 18:38:32 +03:00
										 |  |  |     qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,(int)cache.maxCost()); | 
					
						
							| 
									
										
										
										
											2017-11-13 01:01:15 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | void StaticFileController::service(HttpRequest& request, HttpResponse& response) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-11-20 18:38:26 +01:00
										 |  |  |     QByteArray path = request.getPath(); | 
					
						
							|  |  |  |     service(path, response); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StaticFileController::service(QByteArray& path, HttpResponse& response) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     //QByteArray path=request.getPath();
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     // Check if we have the file in cache
 | 
					
						
							|  |  |  |     qint64 now=QDateTime::currentMSecsSinceEpoch(); | 
					
						
							|  |  |  |     mutex.lock(); | 
					
						
							|  |  |  |     CacheEntry* entry=cache.object(path); | 
					
						
							|  |  |  |     if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock.
 | 
					
						
							|  |  |  |         QByteArray filename=entry->filename; | 
					
						
							|  |  |  |         mutex.unlock(); | 
					
						
							|  |  |  |         qDebug("StaticFileController: Cache hit for %s",path.data()); | 
					
						
							|  |  |  |         setContentType(filename,response); | 
					
						
							|  |  |  |         response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); | 
					
						
							|  |  |  |         response.write(document); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         mutex.unlock(); | 
					
						
							|  |  |  |         // The file is not in cache.
 | 
					
						
							|  |  |  |         qDebug("StaticFileController: Cache miss for %s",path.data()); | 
					
						
							|  |  |  |         // Forbid access to files outside the docroot directory
 | 
					
						
							|  |  |  |         if (path.contains("/..")) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2018-02-11 21:45:44 +01:00
										 |  |  |             qWarning("StaticFileController::service: detected forbidden characters in path %s",path.data()); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |             response.setStatus(403,"forbidden"); | 
					
						
							|  |  |  |             response.write("403 forbidden",true); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // If the filename is a directory, append index.html.
 | 
					
						
							|  |  |  |         if (QFileInfo(docroot+path).isDir()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             path+="/index.html"; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // Try to open the file
 | 
					
						
							|  |  |  |         QFile file(docroot+path); | 
					
						
							|  |  |  |         qDebug("StaticFileController: Open file %s",qPrintable(file.fileName())); | 
					
						
							|  |  |  |         if (file.open(QIODevice::ReadOnly)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             setContentType(path,response); | 
					
						
							|  |  |  |             response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); | 
					
						
							|  |  |  |             if (file.size()<=maxCachedFileSize) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Return the file content and store it also in the cache
 | 
					
						
							|  |  |  |                 entry=new CacheEntry(); | 
					
						
							|  |  |  |                 while (!file.atEnd() && !file.error()) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     QByteArray buffer=file.read(65536); | 
					
						
							|  |  |  |                     response.write(buffer); | 
					
						
							|  |  |  |                     entry->document.append(buffer); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 entry->created=now; | 
					
						
							|  |  |  |                 entry->filename=path; | 
					
						
							|  |  |  |                 mutex.lock(); | 
					
						
							| 
									
										
										
										
											2017-11-20 18:38:26 +01:00
										 |  |  |                 //cache.insert(request.getPath(),entry,entry->document.size());
 | 
					
						
							|  |  |  |                 cache.insert(path,entry,entry->document.size()); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |                 mutex.unlock(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Return the file content, do not store in cache
 | 
					
						
							|  |  |  |                 while (!file.atEnd() && !file.error()) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     response.write(file.read(65536)); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             file.close(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             if (file.exists()) | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2018-02-11 21:45:44 +01:00
										 |  |  |                 qWarning("StaticFileController::service: Cannot open existing file %s for reading",qPrintable(file.fileName())); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |                 response.setStatus(403,"forbidden"); | 
					
						
							|  |  |  |                 response.write("403 forbidden",true); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2018-02-11 21:45:44 +01:00
										 |  |  |                 qWarning("StaticFileController::service: File %s not found",qPrintable(file.fileName())); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |                 response.setStatus(404,"not found"); | 
					
						
							|  |  |  |                 response.write("404 not found",true); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void StaticFileController::setContentType(QString fileName, HttpResponse& response) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (fileName.endsWith(".png")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "image/png"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".jpg")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "image/jpeg"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".gif")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "image/gif"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".pdf")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "application/pdf"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".txt")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".css")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "text/css"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".js")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "text/javascript"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".svg")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "image/svg+xml"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".woff")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "font/woff"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".woff2")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "font/woff2"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".ttf")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "application/x-font-ttf"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".eot")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "application/vnd.ms-fontobject"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (fileName.endsWith(".otf")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "application/font-otf"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-02-18 23:37:59 +01:00
										 |  |  |     else if (fileName.endsWith(".yaml")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         response.setHeader("Content-Type", "text/plain"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     // Todo: add all of your content types
 | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |