@@ -21,7 +21,11 @@ namespace beamui::applications
2121{
2222 AppsServer::AppsServer (const QString& serveFrom, uint32_t port)
2323 : _documentRoot(serveFrom)
24+ , _canonicalRoot(_documentRoot.canonicalPath())
2425 {
26+ if (_canonicalRoot.isEmpty ())
27+ throw std::runtime_error (" document root does not exist" );
28+
2529 _server = std::make_unique<QHttpServer>();
2630
2731 // Use setMissingHandler as a catch-all to serve static files for any path
@@ -33,27 +37,30 @@ namespace beamui::applications
3337
3438 QString absolutePath = _documentRoot.absoluteFilePath (path);
3539
36- // Prevent path traversal outside document root
37- if (!absolutePath.startsWith (_documentRoot.absolutePath ())) {
40+ // Resolve symlinks and ".." to a canonical path for safe comparison
41+ QString canonical = QFileInfo (absolutePath).canonicalFilePath ();
42+ if (canonical.isEmpty () || !canonical.startsWith (_canonicalRoot)) {
3843 responder.write (QHttpServerResponder::StatusCode::Forbidden);
3944 return ;
4045 }
4146
42- QFileInfo fileInfo (absolutePath );
43- if (!fileInfo. exists () || fileInfo.isDir ()) {
47+ QFileInfo fileInfo (canonical );
48+ if (fileInfo.isDir ()) {
4449 responder.write (QHttpServerResponder::StatusCode::NotFound);
4550 return ;
4651 }
4752
48- QFile file (absolutePath);
49- if (!file.open (QIODevice::ReadOnly)) {
53+ // Stream via QIODevice — avoids loading entire file into memory
54+ auto *file = new QFile (canonical);
55+ if (!file->open (QIODevice::ReadOnly)) {
56+ delete file;
5057 responder.write (QHttpServerResponder::StatusCode::Forbidden);
5158 return ;
5259 }
5360
54- QByteArray content = file. readAll ();
55- QByteArray mimeType = _mimeDb. mimeTypeForFile (absolutePath). name (). toUtf8 ();
56- responder.write (content , mimeType);
61+ QByteArray mimeType = _mimeDb. mimeTypeForFile (canonical). name (). toUtf8 ();
62+ // Note: This function takes the ownership of data.
63+ responder.write (file , mimeType);
5764 });
5865
5966 _tcpServer = new QTcpServer ();
@@ -63,7 +70,13 @@ namespace beamui::applications
6370 _tcpServer = nullptr ;
6471 throw std::runtime_error (" failed to listen" );
6572 }
66- _server->bind (_tcpServer);
73+ if (!_server->bind (_tcpServer))
74+ {
75+ // listen() succeeded but bind() failed; _tcpServer was not reparented
76+ delete _tcpServer;
77+ _tcpServer = nullptr ;
78+ throw std::runtime_error (" failed to bind" );
79+ }
6780 }
6881
6982 AppsServer::~AppsServer ()
0 commit comments