#include "cst_util.h" #include #include #include #include #include #include static int buf_reserve(cst_buf_t *b, size_t extra) { if (b->len + extra + 1 <= b->cap) return 0; size_t nc = b->cap ? b->cap : 256; while (nc < b->len + extra + 1) nc *= 2; char *nb = realloc(b->buf, nc); if (!nb) return -1; b->buf = nb; b->cap = nc; return 0; } int cst_buf_append(cst_buf_t *b, const char *s, size_t n) { if (!b) return -1; if (buf_reserve(b, n) != 0) return -1; if (n) memcpy(b->buf + b->len, s, n); b->len += n; b->buf[b->len] = 0; return 0; } int cst_buf_puts(cst_buf_t *b, const char *s) { return cst_buf_append(b, s ? s : "", s ? strlen(s) : 0); } int cst_buf_printf(cst_buf_t *b, const char *fmt, ...) { va_list ap; va_start(ap, fmt); va_list ap2; va_copy(ap2, ap); int n = vsnprintf(NULL, 0, fmt, ap); va_end(ap); if (n < 0 || buf_reserve(b, (size_t)n) != 0) { va_end(ap2); return -1; } vsnprintf(b->buf + b->len, (size_t)n + 1, fmt, ap2); va_end(ap2); b->len += (size_t)n; return 0; } void cst_buf_free(cst_buf_t *b) { if (!b) return; free(b->buf); b->buf = NULL; b->len = b->cap = 0; } char *cst_buf_steal(cst_buf_t *b) { if (buf_reserve(b, 0) != 0) return NULL; char *s = b->buf; b->buf = NULL; b->len = b->cap = 0; return s; } size_t cst_utf8_floor(const char *s, size_t n) { if (!s || n == 0) return 0; size_t i = n; /* walk back over at most 3 continuation bytes to the lead byte */ size_t back = 0; while (i > 0 && back < 3 && ((unsigned char)s[i - 1] & 0xC0) == 0x80) { i--; back++; } if (i == 0) return n; unsigned char lead = (unsigned char)s[i - 1]; size_t need = 1; if (lead >= 0xF0) need = 4; else if (lead >= 0xE0) need = 3; else if (lead >= 0xC0) need = 2; if (need == 1) return n; /* ASCII lead; any continuation bytes after it are invalid anyway and get sanitized later */ if (back + 1 >= need) return n; return i - 1; /* sequence incomplete: cut before lead byte */ } char *cst_utf8_hold_feed(cst_utf8_hold_t *h, const char *s, size_t n, size_t *out_len) { size_t have = h->n; char *all = malloc(have + n + 1); if (!all) return NULL; memcpy(all, h->b, have); if (n) memcpy(all + have, s, n); size_t len = have + n; size_t cut = cst_utf8_floor(all, len); if (len - cut > sizeof(h->b)) cut = len; h->n = len - cut; memcpy(h->b, all + cut, h->n); all[cut] = 0; *out_len = cut; return all; } static size_t utf8_seq_ok(const unsigned char *p, size_t left) { unsigned char c = p[0]; size_t need; unsigned cp; if (c < 0x80) return 1; if ((c & 0xE0) == 0xC0) { need = 2; cp = c & 0x1F; } else if ((c & 0xF0) == 0xE0) { need = 3; cp = c & 0x0F; } else if ((c & 0xF8) == 0xF0) { need = 4; cp = c & 0x07; } else { return 0; } if (left < need) return 0; for (size_t i = 1; i < need; i++) { if ((p[i] & 0xC0) != 0x80) return 0; cp = (cp << 6) | (p[i] & 0x3F); } if ((need == 2 && cp < 0x80) || (need == 3 && cp < 0x800) || (need == 4 && (cp < 0x10000 || cp > 0x10FFFF)) || (cp >= 0xD800 && cp <= 0xDFFF)) return 0; return need; } char *cst_utf8_sanitize(const char *s, size_t n, size_t *out_len) { char *out = malloc(n + 1); if (!out) return NULL; size_t o = 0; const unsigned char *p = (const unsigned char *)(s ? s : ""); size_t i = 0; while (i < n) { size_t k = utf8_seq_ok(p + i, n - i); if (k == 0) { out[o++] = '?'; i++; continue; } memcpy(out + o, p + i, k); o += k; i += k; } out[o] = 0; if (out_len) *out_len = o; return out; } int cst_utf8_valid(const char *s, size_t n) { const unsigned char *p = (const unsigned char *)s; for (size_t i = 0; i < n;) { size_t k = utf8_seq_ok(p + i, n - i); if (k == 0) return 0; i += k; } return 1; } size_t cst_bom_len(const char *s, size_t n) { return n >= 3 && memcmp(s, "\xEF\xBB\xBF", 3) == 0 ? 3 : 0; } void cst_strip_bom(char *s, size_t *len) { if (!s) return; size_t n = len ? *len : strlen(s); if (!cst_bom_len(s, n)) return; memmove(s, s + 3, n - 3 + 1); if (len) *len = n - 3; } size_t cst_crlf_to_lf(char *s, size_t n) { size_t o = 0; for (size_t i = 0; i < n; i++) if (!(s[i] == '\r' && i + 1 < n && s[i + 1] == '\n')) s[o++] = s[i]; if (o < n) s[o] = 0; return o; } int cst_text_is_crlf(const char *s, size_t n) { int crlf = 0; for (size_t i = 0; s && i < n; i++) { if (s[i] != '\n') continue; if (i == 0 || s[i - 1] != '\r') return 0; crlf = 1; } return crlf; } char *cst_text_restyle(const char *s, size_t n, int crlf, int bom, size_t *out_len) { size_t skip = cst_bom_len(s, n), extra = bom ? 3 : 0; for (size_t i = skip; crlf && i < n; i++) extra += s[i] == '\n' && (i == skip || s[i - 1] != '\r'); char *o = malloc(n - skip + extra + 1); if (!o) return NULL; size_t k = 0; if (bom) { memcpy(o, "\xEF\xBB\xBF", 3); k = 3; } for (size_t i = skip; i < n; i++) { if (crlf && s[i] == '\n' && (i == skip || s[i - 1] != '\r')) o[k++] = '\r'; o[k++] = s[i]; } o[k] = 0; if (out_len) *out_len = k; return o; } /* Next code point of a UTF-8 string; a stray byte stands for itself. */ static int utf8_next(const char **ps) { const unsigned char *p = (const unsigned char *)*ps; size_t left = 0; while (left < 4 && p[left]) left++; size_t k = utf8_seq_ok(p, left); if (k <= 1) { *ps += 1; return p[0]; } int c = p[0] & (0x7F >> k); for (size_t i = 1; i < k; i++) c = (c << 6) | (p[i] & 0x3F); *ps += k; return c; } static int fold_cp(int c) { if (c >= 'A' && c <= 'Z') return c + 32; if (c > 0x7F && c <= 0xFFFF) return (int)towlower((wint_t)c); return c; } /* p points just past '['. 1/0 = (no) match with *pp moved past ']'; * -1 = no closing ']' (the '[' is then a literal). */ static int fnm_bracket(const char **pp, int c, int fold) { const char *p = *pp; int neg = 0, hit = 0; if (*p == '!' || *p == '^') { neg = 1; p++; } int fc = fold ? fold_cp(c) : c; for (int first = 1; *p && (*p != ']' || first); first = 0) { if (p[0] == '[' && p[1] == ':') { const char *e = strstr(p + 2, ":]"); if (e) { char name[16]; size_t nl = (size_t)(e - (p + 2)); if (nl < sizeof(name)) { memcpy(name, p + 2, nl); name[nl] = 0; wctype_t t = wctype(name); if (t && c <= 0xFFFF && iswctype((wint_t)c, t)) hit = 1; } p = e + 2; continue; } } if (*p == '\\' && p[1]) p++; int lo = utf8_next(&p), hi = lo; if (p[0] == '-' && p[1] && p[1] != ']') { p++; if (*p == '\\' && p[1]) p++; hi = utf8_next(&p); } if ((c >= lo && c <= hi) || (fold && fc >= fold_cp(lo) && fc <= fold_cp(hi))) hit = 1; } if (*p != ']') return -1; *pp = p + 1; return hit != neg; } int cst_fnmatch(const char *pat, const char *str, int flags) { #ifdef _WIN32 flags |= CST_FNM_CASEFOLD; #endif int fold = (flags & CST_FNM_CASEFOLD) != 0; const char *p = pat, *s = str; const char *star_p = NULL, *star_s = NULL; if ((flags & CST_FNM_PERIOD) && s[0] == '.' && p[0] != '.' && !(p[0] == '\\' && p[1] == '.')) return 1; while (*s) { if (*p == '*') { while (*p == '*') p++; if (!*p) return 0; star_p = p; star_s = s; continue; } const char *sn = s; int c = utf8_next(&sn); if (*p == '?') { p++; s = sn; continue; } if (*p == '[') { const char *q = p + 1; int r = fnm_bracket(&q, c, fold); if (r == 1) { p = q; s = sn; continue; } if (r == 0) goto backtrack; } { const char *q = p; if (*q == '\\' && q[1]) q++; if (*q) { int pc = utf8_next(&q); if (pc == c || (fold && fold_cp(pc) == fold_cp(c))) { p = q; s = sn; continue; } } } backtrack: if (!star_p) return 1; utf8_next(&star_s); s = star_s; p = star_p; } while (*p == '*') p++; return *p ? 1 : 0; } uint64_t cst_fnv1a64(uint64_t h, const void *data, size_t n) { const unsigned char *p = data; for (size_t i = 0; i < n; i++) { h ^= p[i]; h *= 1099511628211ULL; } return h; }