// Copyright Epic Games, Inc. All Rights Reserved. #include "frontend.h" #include #include #include #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #if ZEN_PLATFORM_WINDOWS # include #endif ZEN_THIRD_PARTY_INCLUDES_END #if !defined(ZEN_EMBED_HTML_ZIP) # define ZEN_EMBED_HTML_ZIP ZEN_BUILD_RELEASE #endif #if ZEN_EMBED_HTML_ZIP static unsigned char gHtmlZipData[] = { # include }; #endif namespace zen { //////////////////////////////////////////////////////////////////////////////// HttpFrontendService::HttpFrontendService(std::filesystem::path Directory, HttpStatusService& StatusService) : m_Directory(Directory) , m_StatusService(StatusService) { ZEN_TRACE_CPU("HttpFrontendService::HttpFrontendService"); std::filesystem::path SelfPath = GetRunningExecutablePath(); #if ZEN_EMBED_HTML_ZIP // Load an embedded Zip archive IoBuffer HtmlZipDataBuffer(IoBuffer::Wrap, gHtmlZipData, sizeof(gHtmlZipData) - 1); m_ZipFs = ZipFs(std::move(HtmlZipDataBuffer)); #endif if (m_Directory.empty() && !m_ZipFs) { // Probe for development layout std::error_code ErrorCode; std::filesystem::path Path = SelfPath; while (Path.has_parent_path()) { std::filesystem::path ParentPath = Path.parent_path(); if (ParentPath == Path) { break; } if (IsFile(ParentPath / "xmake.lua", ErrorCode)) { if (ErrorCode) { break; } std::filesystem::path HtmlDir = ParentPath / "src" / "zenserver" / "frontend" / "html"; if (IsDir(HtmlDir, ErrorCode)) { m_Directory = HtmlDir; } break; } Path = ParentPath; } } if (m_ZipFs) { ZEN_INFO("front-end is served from embedded zip"); } else if (!m_Directory.empty()) { ZEN_INFO("front-end is served from '{}'", m_Directory); } else { ZEN_INFO("front-end is NOT AVAILABLE"); } m_StatusService.RegisterHandler("dashboard", *this); } HttpFrontendService::~HttpFrontendService() { m_StatusService.UnregisterHandler("dashboard", *this); } const char* HttpFrontendService::BaseUri() const { return "/dashboard/"; // in order to use the root path we need to remove HttpAddUrlToUrlGroup in HttpSys.cpp } //////////////////////////////////////////////////////////////////////////////// void HttpFrontendService::HandleStatusRequest(zen::HttpServerRequest& Request) { CbObjectWriter Cbo; Cbo << "ok" << true; Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); } void HttpFrontendService::HandleRequest(zen::HttpServerRequest& Request) { using namespace std::literals; std::string_view Uri = Request.RelativeUriWithExtension(); for (; Uri.length() > 0 && Uri[0] == '/'; Uri = Uri.substr(1)) ; if (Uri.empty()) { Uri = "index.html"sv; } // Dismiss if the URI contains .. anywhere to prevent arbitrary file reads if (Uri.find("..") != Uri.npos) { return Request.WriteResponse(HttpResponseCode::Forbidden); } // Map the file extension to a MIME type. To keep things constrained, only a // small subset of file extensions is allowed HttpContentType ContentType = HttpContentType::kUnknownContentType; if (const size_t DotIndex = Uri.rfind("."); DotIndex != Uri.npos) { const std::string_view DotExt = Uri.substr(DotIndex + 1); ContentType = ParseContentType(DotExt); } if (ContentType == HttpContentType::kUnknownContentType) { return Request.WriteResponse(HttpResponseCode::Forbidden); } // The given content directory overrides any zip-fs discovered in the binary if (!m_Directory.empty()) { auto FullPath = m_Directory / std::filesystem::path(Uri).make_preferred(); FileContents File = ReadFile(FullPath); if (!File.ErrorCode) { return Request.WriteResponse(HttpResponseCode::OK, ContentType, File.Data[0]); } } if (IoBuffer FileBuffer = m_ZipFs.GetFile(Uri)) { return Request.WriteResponse(HttpResponseCode::OK, ContentType, FileBuffer); } Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, "Not found"sv); } } // namespace zen