/* util.cpp — string building, UTF-8, filtering, color, IDs, MD5, misc. */ #include "bantam.hpp" #include #include #include #include #include #include #include #include #include /* ----------------------------- formatting ----------------------------- */ static std::string vfmt(const char *fmt, va_list ap) { va_list cp; va_copy(cp, ap); int need = std::vsnprintf(nullptr, 0, fmt, cp); va_end(cp); if (need < 0) return {}; std::string s; s.resize((size_t)need); std::vsnprintf(s.data(), (size_t)need + 1, fmt, ap); return s; } std::string sfmt(const char *fmt, ...) { va_list ap; va_start(ap, fmt); std::string s = vfmt(fmt, ap); va_end(ap); return s; } void str_appendf(std::string &s, const char *fmt, ...) { va_list ap; va_start(ap, fmt); std::string more = vfmt(fmt, ap); va_end(ap); s += more; } /* ----------------------------- UTF-8 ---------------------------------- */ int utf8_decode(const unsigned char *p, size_t avail, size_t *plen) { if (avail == 0) { *plen = 0; return 0xFFFD; } unsigned char b0 = p[0]; if (b0 < 0x80) { *plen = 1; return b0; } if ((b0 & 0xE0) == 0xC0) { if (avail < 2 || (p[1] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; } int cp = ((b0 & 0x1F) << 6) | (p[1] & 0x3F); if (cp < 0x80) { *plen = 1; return 0xFFFD; } *plen = 2; return cp; } if ((b0 & 0xF0) == 0xE0) { if (avail < 3 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; } int cp = ((b0 & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F); if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF)) { *plen = 1; return 0xFFFD; } *plen = 3; return cp; } if ((b0 & 0xF8) == 0xF0) { if (avail < 4 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80) { *plen = 1; return 0xFFFD; } int cp = ((b0 & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); if (cp < 0x10000 || cp > 0x10FFFF) { *plen = 1; return 0xFFFD; } *plen = 4; return cp; } *plen = 1; return 0xFFFD; } std::string_view utf8_truncate(std::string_view s, size_t max_bytes) { if (s.size() <= max_bytes) return s; const unsigned char *p = (const unsigned char *)s.data(); size_t pos = 0, avail = s.size(); while (avail > 0) { size_t pl; utf8_decode(p + pos, avail, &pl); if (pl == 0 || pos + pl > max_bytes) break; pos += pl; avail -= pl; } return s.substr(0, pos); } /* ----------------------------- filtering ------------------------------ */ static bool kept_rune(int r) { if (r == ' ' || r == '\t' || r == '\n') return true; if (r < 0x20) return false; /* C0 controls */ if (r == 0x7f) return false; /* DEL */ if (r >= 0x80 && r <= 0x9f) return false; /* C1 controls */ switch (r) { case 0x00a0: case 0x00ad: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: case 0x200b: case 0x200c: case 0x200d: case 0x2060: case 0xfeff: case 0x200e: case 0x200f: case 0x202a: case 0x202b: case 0x202c: case 0x202d: case 0x202e: return false; } return true; } std::string filter_text(std::string_view s) { std::string out; out.reserve(s.size()); const unsigned char *p = (const unsigned char *)s.data(); size_t avail = s.size(); while (avail > 0) { size_t pl; int cp = utf8_decode(p, avail, &pl); if (cp == 0xFFFD && pl == 1) { p++; avail--; continue; } if (kept_rune(cp)) out.append((const char *)p, pl); p += pl; avail -= pl; } return out; } /* ----------------------------- color ---------------------------------- */ int g_col = 0; volatile int g_interrupted = 0; std::string cstr(std::string_view text, std::span codes) { if (!g_col || codes.empty() || text.empty()) return std::string(text); std::string s; s.reserve(text.size() + 16); s += "\033["; for (size_t i = 0; i < codes.size(); i++) { if (i) s += ';'; s += sfmt("%d", codes[i]); } s += "m"; s += text; s += "\033[0m"; return s; } void cprint(std::string_view text, std::span codes) { std::string w = cstr(text, codes); std::fputs(w.c_str(), stdout); } int visible_len(std::string_view s) { int n = 0; const unsigned char *p = (const unsigned char *)s.data(); size_t avail = s.size(); while (avail > 0) { if (p[0] == 0x1b) { size_t i = 1; if (i < avail && p[1] == '[') { i++; while (i < avail && !(p[i] >= 0x40 && p[i] <= 0x7e)) i++; if (i < avail) i++; } else { i = 1; } p += i; avail -= i; continue; } if (p[0] == 0x01 || p[0] == 0x02) { p++; avail--; continue; } size_t pl; int cp = utf8_decode(p, avail, &pl); n += rune_width(cp); p += pl; avail -= pl; } return n; } /* Display width of a single Unicode code point. Most code points occupy one * terminal column, but East-Asian Wide/Fullwidth characters (notably CJK) and * many emoji occupy two. Combining marks and zero-width characters occupy * none. Without this, tables containing Chinese text were misaligned because * visible_len counted every rune as a single column. */ int rune_width(int cp) { if (cp < 0x20) return 0; if (cp == 0x7f) return 0; /* combining marks / zero-width */ if ((cp >= 0x0300 && cp <= 0x036f) || (cp >= 0x0483 && cp <= 0x0489) || (cp >= 0x0591 && cp <= 0x05bd) || (cp >= 0x0610 && cp <= 0x061a) || (cp >= 0x064b && cp <= 0x065f) || (cp >= 0x0670 && cp <= 0x0670) || (cp >= 0x06d6 && cp <= 0x06dc) || (cp >= 0x0e31 && cp <= 0x0e3a) || (cp >= 0x0e47 && cp <= 0x0e4e) || (cp >= 0x0eb1 && cp <= 0x0eb9) || (cp >= 0x0ebb && cp <= 0x0ebd) || (cp >= 0x0ec8 && cp <= 0x0ecd) || (cp >= 0x200b && cp <= 0x200f) || (cp >= 0x202a && cp <= 0x202e) || (cp >= 0x2060 && cp <= 0x2064) || (cp >= 0xfe00 && cp <= 0xfe0f) || (cp == 0xfeff) || (cp >= 0xfff9 && cp <= 0xfffb) || (cp >= 0x1d173 && cp <= 0x1d17a)) return 0; /* East Asian Wide / Fullwidth */ if (cp == 0x3000) return 2; if (cp >= 0x1100 && cp <= 0x115f) return 2; if (cp >= 0x2e80 && cp <= 0x303e) return 2; if (cp >= 0x3041 && cp <= 0x33ff) return 2; if (cp >= 0x3400 && cp <= 0x4dbf) return 2; if (cp >= 0x4e00 && cp <= 0x9fff) return 2; if (cp >= 0xa000 && cp <= 0xa4cf) return 2; if (cp >= 0xac00 && cp <= 0xd7a3) return 2; if (cp >= 0xf900 && cp <= 0xfaff) return 2; if (cp >= 0xfe30 && cp <= 0xfe4f) return 2; if (cp >= 0xff00 && cp <= 0xff60) return 2; if (cp >= 0xffe0 && cp <= 0xffe6) return 2; if (cp >= 0x1f300 && cp <= 0x1faff) return 2; if (cp >= 0x20000 && cp <= 0x3fffd) return 2; return 1; } /* ----------------------------- paths ---------------------------------- */ void mkdir_p(std::string_view dir) { if (dir.empty()) return; std::string path(dir); for (size_t i = 1; i < path.size(); i++) { if (path[i] == '/') { path[i] = '\0'; mkdir(path.c_str(), 0755); path[i] = '/'; } } mkdir(path.c_str(), 0755); } std::string bantam_tmp_dir() { const char *tmp = std::getenv("TMPDIR"); if (!tmp || !*tmp) tmp = "/tmp"; std::string p = std::string(tmp) + "/bantam"; mkdir_p(p); return p; } std::string tool_res_dir() { std::string base = bantam_tmp_dir(); std::string p = base + "/toolres"; mkdir_p(p); return p; } std::string project_id() { char cwd[4096]; if (!getcwd(cwd, sizeof cwd)) std::strcpy(cwd, "."); char *abs = realpath(cwd, nullptr); std::string src = abs ? abs : cwd; if (abs) free(abs); return md5_hex(src); } /* ----------------------------- MD5 ------------------------------------ */ #define MD5_ROTLEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) struct MD5_CTX { uint32_t state[4]; uint32_t count[2]; unsigned char buf[64]; }; static void md5_transform(uint32_t state[4], const unsigned char block[64]); static void md5_init(MD5_CTX *c) { c->state[0] = 0x67452301; c->state[1] = 0xefcdab89; c->state[2] = 0x98badcfe; c->state[3] = 0x10325476; c->count[0] = c->count[1] = 0; } static void md5_update(MD5_CTX *c, const unsigned char *in, size_t len) { uint32_t bits = (uint32_t)(c->count[0] >> 3) & 0x3F; if ((c->count[0] += (uint32_t)(len << 3)) < (uint32_t)(len << 3)) c->count[1]++; c->count[1] += (uint32_t)(len >> 29); while (len--) { c->buf[bits++] = *in++; if (bits == 64) { md5_transform(c->state, c->buf); bits = 0; } } } static void md5_final(unsigned char digest[16], MD5_CTX *c) { unsigned char bits[8]; for (int i = 0; i < 8; i++) bits[i] = (unsigned char)((c->count[i >> 2] >> ((i & 3) * 8)) & 0xFF); unsigned char pad = (unsigned char)((c->count[0] >> 3) & 0x3F); unsigned char padlen = (pad < 56) ? (56 - pad) : (120 - pad); unsigned char p = 0x80; md5_update(c, &p, 1); unsigned char zero = 0; for (int i = 0; i < padlen - 1; i++) md5_update(c, &zero, 1); md5_update(c, bits, 8); for (int i = 0; i < 4; i++) { digest[i * 4 + 0] = (unsigned char)(c->state[i] & 0xFF); digest[i * 4 + 1] = (unsigned char)((c->state[i] >> 8) & 0xFF); digest[i * 4 + 2] = (unsigned char)((c->state[i] >> 16) & 0xFF); digest[i * 4 + 3] = (unsigned char)((c->state[i] >> 24) & 0xFF); } } static void md5_transform(uint32_t state[4], const unsigned char b[64]) { uint32_t a = state[0], d = state[3], c = state[2], e = state[1]; uint32_t x[16]; for (int i = 0, j = 0; i < 16; i++, j += 4) x[i] = (uint32_t)b[j] | ((uint32_t)b[j+1] << 8) | ((uint32_t)b[j+2] << 16) | ((uint32_t)b[j+3] << 24); static const uint32_t K[64] = { 0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501, 0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821, 0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8, 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a, 0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70, 0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665, 0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1, 0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391}; static const int S[64] = { 7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22, 5,9,14,20, 5,9,14,20, 5,9,14,20, 5,9,14,20, 4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23, 6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21}; for (int i = 0; i < 64; i++) { uint32_t f; int g; if (i < 16) { f = (e & c) | (~e & d); g = i; } else if (i < 32) { f = (d & e) | (~d & c); g = (5 * i + 1) % 16; } else if (i < 48) { f = e ^ c ^ d; g = (3 * i + 5) % 16; } else { f = c ^ (e | (~d)); g = (7 * i) % 16; } f = f + a + K[i] + x[g]; a = d; d = c; c = e; e = e + MD5_ROTLEFT(f, S[i]); } state[0] += a; state[1] += e; state[2] += c; state[3] += d; } std::string md5_hex(std::string_view data) { MD5_CTX ctx; md5_init(&ctx); md5_update(&ctx, (const unsigned char *)data.data(), data.size()); unsigned char dig[16]; md5_final(dig, &ctx); std::string out; out.reserve(33); static const char *hx = "0123456789abcdef"; for (int i = 0; i < 16; i++) { out += hx[dig[i] >> 4]; out += hx[dig[i] & 0xF]; } return out; } /* ----------------------------- misc ----------------------------------- */ int to_int_val(std::string_view s, int def) { if (s.empty()) return def; std::string tmp(s); char *end; long v = std::strtol(tmp.c_str(), &end, 10); if (end == tmp.c_str() || *end != '\0') return def; return (int)v; } bool read_file_str(std::string_view path, std::string &out) { out.clear(); std::string p(path); FILE *f = std::fopen(p.c_str(), "rb"); if (!f) return false; char buf[4096]; size_t n; while ((n = std::fread(buf, 1, sizeof buf, f)) > 0) { out.append(buf, n); } bool ok = (std::ferror(f) == 0); std::fclose(f); return ok; } bool write_file_str(std::string_view path, std::string_view data) { std::string p(path); FILE *f = std::fopen(p.c_str(), "wb"); if (!f) return false; size_t written = 0; while (written < data.size()) { size_t n = std::fwrite(data.data() + written, 1, data.size() - written, f); if (n == 0) { std::fclose(f); return false; } written += n; } return std::fclose(f) == 0; } /* ----------------------------- signal --------------------------------- */ static void sigint_handler(int sig) { (void)sig; g_interrupted = 1; } void setup_signal_handler() { struct sigaction sa; std::memset(&sa, 0, sizeof sa); sa.sa_handler = sigint_handler; sigaction(SIGINT, &sa, nullptr); } int color_enabled(const Cfg &cfg) { const char *nc = std::getenv("NO_COLOR"); if (nc && *nc) return 0; const char *bnc = std::getenv("BANTAM_NO_COLOR"); if (bnc && *bnc) return 0; const std::string &c = cfg.color; if (c == "always") return 1; if (c == "never") return 0; return is_terminal(STDOUT_FILENO); }