#include "pch.h"
#include "WebAppCore/Server/WebAppServer.h"
#include "WebAppCore/Http/HttpContext.h"
#include "System/IO/File.h"
#include "System/IO/Path.h"
#include "System/ArgumentException.h"
#include "System/Console.h"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <filesystem>
#include <unordered_map>

namespace DotNetDupe {
    namespace WebAppCore {
        namespace Server {

            WebAppServer::WebAppServer(const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::String& webRoot)
                : m_spApp(app), m_sWebRoot(webRoot), m_sDefaultDocument("index.html"), m_bStaticFilesEnabled(true) {
                if (app.IsNull()) {
                    throw DotNetDupe::System::ArgumentException("WebApplication cannot be null");
                }
            }

            WebAppServer::WebAppServer(WebAppServer&& other) noexcept
                : m_spApp(std::move(other.m_spApp)),
                  m_sWebRoot(std::move(other.m_sWebRoot)),
                  m_sDefaultDocument(std::move(other.m_sDefaultDocument)),
                  m_bStaticFilesEnabled(other.m_bStaticFilesEnabled) {
            }

            WebAppServer& WebAppServer::operator=(WebAppServer&& other) noexcept {
                if (this != &other) {
                    m_spApp = std::move(other.m_spApp);
                    m_sWebRoot = std::move(other.m_sWebRoot);
                    m_sDefaultDocument = std::move(other.m_sDefaultDocument);
                    m_bStaticFilesEnabled = other.m_bStaticFilesEnabled;
                }
                return *this;
            }

            void WebAppServer::EnableStaticFiles(const DotNetDupe::System::String& defaultDocument) {
                m_bStaticFilesEnabled = true;
                m_sDefaultDocument = defaultDocument;
            }

            DotNetDupe::System::String WebAppServer::GetMimeType(const DotNetDupe::System::String& filePath) {
                DotNetDupe::System::String ext = DotNetDupe::System::IO::Path::GetExtension(filePath);
                std::string s = ext.GetRawString();
                std::transform(s.begin(), s.end(), s.begin(), ::tolower);
                if (s.rfind('.', 0) == 0) s = s.substr(1);
                static const std::unordered_map<std::string, const char*> mimeMap = {
                    {"html", "text/html; charset=utf-8"}, {"htm", "text/html; charset=utf-8"},
                    {"css", "text/css; charset=utf-8"}, {"js", "application/javascript; charset=utf-8"},
                    {"json", "application/json; charset=utf-8"}, {"png", "image/png"},
                    {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"gif", "image/gif"},
                    {"svg", "image/svg+xml"}, {"ico", "image/x-icon"}, {"txt", "text/plain; charset=utf-8"},
                    {"pdf", "application/pdf"}, {"woff", "font/woff"}, {"woff2", "font/woff2"}
                };
                auto it = mimeMap.find(s);
                return (it != mimeMap.end()) ? it->second : "application/octet-stream";
            }

            static DotNetDupe::System::String ResolveStaticPath(const DotNetDupe::System::String& reqPath, const DotNetDupe::System::String& webRoot, const DotNetDupe::System::String& defaultDoc) {
                std::string rel = reqPath.GetRawString();
                if (!rel.empty() && (rel[0] == '/' || rel[0] == '\\')) rel = rel.substr(1);
                if (rel.empty()) rel = defaultDoc.GetRawString();
                if (rel.find("..") != std::string::npos) return "";
                DotNetDupe::System::String target = DotNetDupe::System::IO::Path::Combine({webRoot, DotNetDupe::System::String(rel.c_str())});
                DotNetDupe::System::IO::FileAttributes attr;
                if (DotNetDupe::System::IO::File::GetAttributes(target, attr) && (static_cast<int>(attr) & static_cast<int>(DotNetDupe::System::IO::FileAttributes::Directory)) != 0) {
                    target = DotNetDupe::System::IO::Path::Combine({target, defaultDoc});
                }
                return target;
            }

            static DotNetDupe::System::String ServeStaticFile(DotNetDupe::System::SmartPointer<Http::HttpContext> spContext, const DotNetDupe::System::String& webRoot, const DotNetDupe::System::String& defaultDoc) {
                DotNetDupe::System::String target = ResolveStaticPath(spContext->GetRequest()->GetPath(), webRoot, defaultDoc);
                if (target.IsEmpty()) {
                    spContext->GetResponse()->SetStatusCode(403); spContext->GetResponse()->SetContentType("text/plain");
                    return "403 Forbidden: Invalid file path";
                }
                if (!DotNetDupe::System::IO::File::Exists(target)) {
                    spContext->GetResponse()->SetStatusCode(404); spContext->GetResponse()->SetContentType("text/plain");
                    return "404 Not Found";
                }
                try {
                    DotNetDupe::System::String content = DotNetDupe::System::IO::File::ReadAllText(target);
                    spContext->GetResponse()->SetStatusCode(200);
                    spContext->GetResponse()->SetContentType(WebAppServer::GetMimeType(target));
                    spContext->GetResponse()->GetHeaders()["Content-Disposition"] = "inline";
                    return content;
                } catch (...) {
                    spContext->GetResponse()->SetStatusCode(500); spContext->GetResponse()->SetContentType("text/plain");
                    return "500 Internal Server Error: Unable to open file";
                }
            }

            static DotNetDupe::System::String HandleStaticRouteOrWebSocket(const DotNetDupe::System::SmartPointer<Builder::WebApplication>& spApp, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx, const DotNetDupe::System::String& webRoot, const DotNetDupe::System::String& defaultDoc) {
                if (spApp->HasWebSocketRoute(ctx->GetRequest()->GetPath())) {
                    ctx->GetResponse()->SetStatusCode(426);
                    ctx->GetResponse()->SetContentType("text/plain");
                    ctx->GetResponse()->GetHeaders()["Upgrade"] = "websocket";
                    ctx->GetResponse()->GetHeaders()["Connection"] = "Upgrade";
                    return "426 Upgrade Required";
                }
                return ServeStaticFile(ctx, webRoot, defaultDoc);
            }

            void WebAppServer::Run(const DotNetDupe::System::String& url, int threadCount) {
                if (m_bStaticFilesEnabled) {
                    m_spApp->MapGet("/{*filepath}", [this](DotNetDupe::System::SmartPointer<Http::HttpContext> ctx) {
                        return HandleStaticRouteOrWebSocket(m_spApp, ctx, m_sWebRoot, m_sDefaultDocument);
                    });
                }
                m_spApp->Run(url, threadCount);
            }

            void WebAppServer::Stop() {
                m_spApp->Stop();
            }

        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)