#include "pch.h"
#include "System/Uri.h"
#include <string>
#include <algorithm>
#include <cctype>

namespace DotNetDupe {
    namespace System {
        Uri::Uri(const String& uriString) : _uriString(uriString) {
            ParseUri();
        }

        static void ParseSchemeAndAuthority(const std::string& rawUri, String& scheme, String& authority) {
            size_t schemeEnd = rawUri.find(':');
            if (schemeEnd == std::string::npos) return;
            scheme = String(rawUri.substr(0, schemeEnd).c_str());
            size_t authStart = schemeEnd + 1;
            if (authStart + 1 < rawUri.length() && rawUri.substr(authStart, 2) == "//") {
                authStart += 2;
                size_t authEnd = rawUri.find_first_of("/?#", authStart);
                authority = (authEnd == std::string::npos) 
                    ? String(rawUri.substr(authStart).c_str())
                    : String(rawUri.substr(authStart, authEnd - authStart).c_str());
            }
        }

        static void ParsePath(const std::string& rawUri, const String& scheme, String& path) {
            size_t pathStart = rawUri.find("//");
            if (pathStart != std::string::npos) {
                pathStart = rawUri.find('/', pathStart + 2);
            } else {
                size_t colon = rawUri.find(':');
                pathStart = (colon != std::string::npos) 
                    ? (scheme.Equals("mailto") ? colon + 1 : rawUri.find('/', colon + 1)) 
                    : 0;
            }
            if (pathStart != std::string::npos) {
                size_t pathEnd = rawUri.find_first_of("?#", pathStart);
                path = (pathEnd == std::string::npos)
                    ? String(rawUri.substr(pathStart).c_str())
                    : String(rawUri.substr(pathStart, pathEnd - pathStart).c_str());
            }
            if (path.IsEmpty()) path = String("/");
        }

        static void ParseQueryAndFragment(const std::string& rawUri, String& query, String& fragment) {
            size_t qStart = rawUri.find('?');
            if (qStart != std::string::npos) {
                size_t qEnd = rawUri.find('#', qStart);
                query = (qEnd == std::string::npos)
                    ? String(rawUri.substr(qStart + 1).c_str())
                    : String(rawUri.substr(qStart + 1, qEnd - qStart - 1).c_str());
            }
            size_t fStart = rawUri.find('#');
            if (fStart != std::string::npos) {
                fragment = String(rawUri.substr(fStart + 1).c_str());
            }
        }

        static void ParseAuthorityComponents(const String& authority, const String& scheme, String& userInfo, String& host, int& port) {
            std::string authStr = (const char*)authority;
            if (authStr.empty()) return;
            size_t userEnd = authStr.find('@');
            if (userEnd != std::string::npos) {
                userInfo = String(authStr.substr(0, userEnd).c_str());
                authStr = authStr.substr(userEnd + 1);
            }
            size_t portStart = authStr.find(':');
            if (portStart != std::string::npos) {
                host = String(authStr.substr(0, portStart).c_str());
                port = std::stoi(authStr.substr(portStart + 1));
            } else {
                host = String(authStr.c_str());
                port = (scheme.ToLower() == "https") ? 443 : 80;
            }
        }

        void Uri::ParseUri() {
            std::string rawUri = (const char*)_uriString;
            ParseSchemeAndAuthority(rawUri, _scheme, _authority);
            ParsePath(rawUri, _scheme, _path);
            ParseQueryAndFragment(rawUri, _query, _fragment);
            ParseAuthorityComponents(_authority, _scheme, _userInfo, _host, _port);
        }

        String Uri::GetScheme() const { return _scheme; }
        String Uri::GetAbsoluteUri() const { return _uriString; }
        String Uri::GetAuthority() const { return _authority; }
        String Uri::GetHost() const { return _host; }
        int Uri::GetPort() const { return _port; }
        String Uri::GetAbsolutePath() const { return _path; }
        String Uri::GetQuery() const { return _query; }
        String Uri::GetFragment() const { return _fragment; }
        String Uri::GetUserInfo() const { return _userInfo; }
        String Uri::GetOriginalString() const { return _uriString; }
        
        String Uri::ToString() const {
            return _uriString;
        }

        bool Uri::IsDefaultPort() const {
            if (_scheme.Equals("http") && _port == 80) return true;
            if (_scheme.Equals("https") && _port == 443) return true;
            return false;
        }

        bool Uri::IsFile() const {
            return _scheme.Equals("file");
        }

        bool Uri::IsLoopback() const {
            return _host.Equals("localhost") || _host.Equals("127.0.0.1") || _host.Equals("::1");
        }

        String Uri::EscapeDataString(const String& stringToEscape) {
            std::string s = (const char*)stringToEscape;
            std::string escaped;
            for (char c : s) {
                if (isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~') {
                    escaped += c;
                } else {
                    char buf[4];
                    snprintf(buf, 4, "%%%02X", static_cast<unsigned char>(c));
                    escaped += buf;
                }
            }
            return String(escaped.c_str());
        }

        static void UnescapePercentChar(const std::string& s, size_t& i, std::string& unescaped) {
            int value;
#if defined(_WIN32)
            if (sscanf_s(s.substr(i + 1, 2).c_str(), "%x", &value) == 1) {
#else
            if (sscanf(s.substr(i + 1, 2).c_str(), "%x", &value) == 1) {
#endif
                unescaped += static_cast<char>(value);
                i += 2;
            } else {
                unescaped += s[i];
            }
        }

        String Uri::UnescapeDataString(const String& stringToUnescape) {
            std::string s = (const char*)stringToUnescape;
            std::string unescaped;
            for (size_t i = 0; i < s.length(); ++i) {
                if (s[i] == '%' && i + 2 < s.length()) {
                    UnescapePercentChar(s, i, unescaped);
                } else {
                    unescaped += s[i];
                }
            }
            return String(unescaped.c_str());
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)