git repos / sago_web_git

src/util.cpp

browsing at commit = 55fa1ac0b398c748c7e2421b855a1606790cafc9

raw · blame · history

#include "util.h"

#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>

namespace util {

namespace {
constexpr int MIN_OID_LEN = 4;
constexpr int MAX_OID_LEN = 40;
} // namespace

bool is_hex_oid(const std::string &s) {
    if (s.length() < MIN_OID_LEN || s.length() > MAX_OID_LEN)
        return false;
    for (char c : s) {
        const bool is_digit = c >= '0' && c <= '9';
        const bool is_lower_hex = c >= 'a' && c <= 'f';
        if (!is_digit && !is_lower_hex)
            return false;
    }
    return true;
}

std::string human_size(uint64_t bytes) {
    constexpr uint64_t unit = 1024;
    const std::array<const char *, 5> suffixes{"B", "KiB", "MiB", "GiB", "TiB"};
    if (bytes < unit)
        return std::to_string(bytes) + " B";

    double value = static_cast<double>(bytes);
    size_t i = 0;
    while (value >= unit && i + 1 < suffixes.size()) {
        value /= unit;
        ++i;
    }
    std::array<char, 32> buf{};
    std::snprintf(buf.data(), buf.size(), "%.1f %s", value, suffixes[i]);
    return std::string(buf.data());
}

std::string format_time(std::time_t t) {
    std::tm tm{};
    gmtime_r(&t, &tm);
    std::array<char, 32> buf{};
    std::strftime(buf.data(), buf.size(), "%Y-%m-%d %H:%M", &tm);
    return std::string(buf.data());
}

std::string basename(const std::string &path) {
    const size_t pos = path.find_last_of('/');
    if (pos == std::string::npos)
        return path;
    return path.substr(pos + 1);
}

std::string parent_path(const std::string &path) {
    const size_t pos = path.find_last_of('/');
    if (pos == std::string::npos)
        return "";
    return path.substr(0, pos);
}

std::string trim_slashes(const std::string &path) {
    size_t start = 0;
    size_t end = path.length();
    while (start < end && path[start] == '/')
        ++start;
    while (end > start && path[end - 1] == '/')
        --end;
    return path.substr(start, end - start);
}

std::string sanitize_filename(const std::string &name) {
    std::string out = name;
    for (char &c : out) {
        const bool is_digit = c >= '0' && c <= '9';
        const bool is_upper = c >= 'A' && c <= 'Z';
        const bool is_lower = c >= 'a' && c <= 'z';
        const bool is_safe_punct = c == '.' || c == '_' || c == '-';
        if (!is_digit && !is_upper && !is_lower && !is_safe_punct)
            c = '_';
    }
    return out;
}

bool is_markdown_path(const std::string &path) {
    const size_t dot = path.find_last_of('.');
    if (dot == std::string::npos)
        return false;
    std::string ext = path.substr(dot + 1);
    std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
    return ext == "md" || ext == "markdown";
}

std::string image_mime_type(const std::string &path) {
    const size_t dot = path.find_last_of('.');
    if (dot == std::string::npos)
        return "";
    std::string ext = path.substr(dot + 1);
    std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
    if (ext == "png")
        return "image/png";
    if (ext == "jpg" || ext == "jpeg")
        return "image/jpeg";
    if (ext == "gif")
        return "image/gif";
    if (ext == "webp")
        return "image/webp";
    if (ext == "svg")
        return "image/svg+xml";
    if (ext == "bmp")
        return "image/bmp";
    if (ext == "ico")
        return "image/x-icon";
    if (ext == "avif")
        return "image/avif";
    return "";
}

namespace {

// Splits a remote URL into host and path ("owner/repo[.git]"). Returns false
// when the URL has no recognisable host/path split.
bool split_remote(const std::string &url, std::string &host, std::string &path) {
    const std::string scheme_sep = "://";
    const size_t scheme = url.find(scheme_sep);
    if (scheme != std::string::npos) {
        // scheme://[user@]host[:port]/owner/repo
        std::string rest = url.substr(scheme + scheme_sep.length());
        const size_t at = rest.find('@');
        if (at != std::string::npos)
            rest = rest.substr(at + 1);
        const size_t slash = rest.find('/');
        if (slash == std::string::npos)
            return false;
        host = rest.substr(0, slash);
        path = rest.substr(slash + 1);
        const size_t port = host.find(':');
        if (port != std::string::npos)
            host = host.substr(0, port);
        return true;
    }
    // scp-like: [user@]host:owner/repo
    const size_t colon = url.find(':');
    if (colon == std::string::npos)
        return false;
    std::string host_part = url.substr(0, colon);
    path = url.substr(colon + 1);
    const size_t at = host_part.find('@');
    host = at == std::string::npos ? host_part : host_part.substr(at + 1);
    return true;
}

bool is_github_host(std::string host, const std::vector<std::string> &enterprise_hosts) {
    std::transform(host.begin(), host.end(), host.begin(), ::tolower);
    if (host == "github.com")
        return true;
    for (const std::string &configured : enterprise_hosts) {
        std::string h = configured;
        std::transform(h.begin(), h.end(), h.begin(), ::tolower);
        if (host == h)
            return true;
    }
    return false;
}

} // namespace

GitHubClone github_clone_urls(const std::string &remote_url,
                              const std::vector<std::string> &enterprise_hosts) {
    GitHubClone result;
    std::string host;
    std::string path;
    if (!split_remote(remote_url, host, path))
        return result;
    if (!is_github_host(host, enterprise_hosts))
        return result;

    while (path.length() > 0 && path.front() == '/')
        path.erase(path.begin());
    while (path.length() > 0 && path.back() == '/')
        path.pop_back();
    const std::string git_suffix = ".git";
    if (path.length() >= git_suffix.length() &&
        path.compare(path.length() - git_suffix.length(), git_suffix.length(),
                     git_suffix) == 0)
        path = path.substr(0, path.length() - git_suffix.length());
    if (path.empty())
        return result;

    result.ssh = "git@" + host + ":" + path + ".git";
    result.https = "https://" + host + "/" + path + ".git";
    return result;
}

bool show_emails() {
    static const bool value = [] {
        const char *flag = std::getenv("SHOW_EMAILS");
        return flag && std::string(flag) == "1";
    }();
    return value;
}

} // namespace util