// 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 = std::make_unique(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; ExtendableStringBuilder<256> UriBuilder; std::string_view Uri = Request.RelativeUriWithExtension(); for (; Uri.length() > 0 && Uri[0] == '/'; Uri = Uri.substr(1)) ; if (Uri.empty()) { Uri = "index.html"sv; } else if (Uri.back() == '/') { UriBuilder << Uri << "index.html"sv; Uri = UriBuilder; } // 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); } auto WriteResponseForUri = [this, &Request](std::string_view InUri, HttpResponseCode ResponseCode, HttpContentType ContentType) -> bool { // The given content directory overrides any zip-fs discovered in the binary if (!m_Directory.empty()) { auto FullPath = m_Directory / std::filesystem::path(InUri).make_preferred(); FileContents File = ReadFile(FullPath); if (!File.ErrorCode) { Request.WriteResponse(ResponseCode, ContentType, File.Data[0]); return true; } } if (m_ZipFs) { if (IoBuffer FileBuffer = m_ZipFs->GetFile(InUri)) { Request.WriteResponse(HttpResponseCode::OK, ContentType, FileBuffer); return true; } } return false; }; if (WriteResponseForUri(Uri, HttpResponseCode::OK, ContentType)) { return; } else if (WriteResponseForUri("404.html"sv, HttpResponseCode::NotFound, HttpContentType::kHTML)) { return; } else { Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, "Not found"sv); } } } // namespace zen