#include "util.h" #include #include #include #include #include #include #include #include #include "sha256.h" void *xmalloc(size_t n) { void *p = malloc(n ? n : 1); if (!p) { perror("malloc"); exit(1); } return p; } void *xcalloc(size_t n, size_t sz) { void *p = calloc(n ? n : 1, sz ? sz : 1); if (!p) { perror("calloc"); exit(1); } return p; } void *xrealloc(void *p, size_t n) { void *q = realloc(p, n ? n : 1); if (!q) { perror("realloc"); exit(1); } return q; } char *xstrdup(const char *s) { char *p = strdup(s); if (!p) { perror("strdup"); exit(1); } return p; } void buf_init(struct buf *b) { b->p = NULL; b->len = 0; b->cap = 0; } void buf_free(struct buf *b) { free(b->p); b->p = NULL; b->len = b->cap = 0; } static void buf_reserve(struct buf *b, size_t extra) { if (b->len + extra <= b->cap) return; size_t nc = b->cap ? b->cap : 64; while (nc < b->len + extra) nc *= 2; b->p = xrealloc(b->p, nc); b->cap = nc; } void buf_append(struct buf *b, const void *data, size_t n) { if (!n) return; buf_reserve(b, n); memcpy(b->p + b->len, data, n); b->len += n; } void buf_append_u16be(struct buf *b, uint16_t v) { unsigned char t[2] = { (unsigned char)(v >> 8), (unsigned char)v }; buf_append(b, t, sizeof t); } void buf_append_u32be(struct buf *b, uint32_t v) { unsigned char t[4] = { (unsigned char)(v >> 24), (unsigned char)(v >> 16), (unsigned char)(v >> 8), (unsigned char)v }; buf_append(b, t, sizeof t); } void buf_append_u64be(struct buf *b, uint64_t v) { unsigned char t[8]; for (int i = 0; i < 8; i++) t[i] = (unsigned char)(v >> (56 - 8 * i)); buf_append(b, t, sizeof t); } void util_sha256(const void *data, size_t n, unsigned char out[32]) { SHA256_CTX ctx; sha256_init(&ctx); sha256_update(&ctx, (const BYTE *)data, n); sha256_final(&ctx, out); } void util_hex(const unsigned char *in, size_t n, char *out) { static const char hex[] = "0123456789abcdef"; for (size_t i = 0; i < n; i++) { out[2 * i] = hex[in[i] >> 4]; out[2 * i + 1] = hex[in[i] & 15]; } out[2 * n] = '\0'; } static const char B64URL[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; char *util_b64url(const unsigned char *in, size_t n) { size_t olen = ((n + 2) / 3) * 4 + 1; char *out = xmalloc(olen); size_t i = 0, o = 0; while (i + 3 <= n) { uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8) | (uint32_t)in[i + 2]; out[o++] = B64URL[(v >> 18) & 63]; out[o++] = B64URL[(v >> 12) & 63]; out[o++] = B64URL[(v >> 6) & 63]; out[o++] = B64URL[v & 63]; i += 3; } if (i + 1 == n) { uint32_t v = (uint32_t)in[i] << 16; out[o++] = B64URL[(v >> 18) & 63]; out[o++] = B64URL[(v >> 12) & 63]; } else if (i + 2 == n) { uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8); out[o++] = B64URL[(v >> 18) & 63]; out[o++] = B64URL[(v >> 12) & 63]; out[o++] = B64URL[(v >> 6) & 63]; } out[o] = '\0'; return out; } char *util_b64(const unsigned char *in, size_t n) { static const char B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; size_t olen = ((n + 2) / 3) * 4 + 1; char *out = xmalloc(olen); size_t i = 0, o = 0; while (i + 3 <= n) { uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8) | (uint32_t)in[i + 2]; out[o++] = B64[(v >> 18) & 63]; out[o++] = B64[(v >> 12) & 63]; out[o++] = B64[(v >> 6) & 63]; out[o++] = B64[v & 63]; i += 3; } if (i + 1 == n) { uint32_t v = (uint32_t)in[i] << 16; out[o++] = B64[(v >> 18) & 63]; out[o++] = B64[(v >> 12) & 63]; out[o++] = '='; out[o++] = '='; } else if (i + 2 == n) { uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8); out[o++] = B64[(v >> 18) & 63]; out[o++] = B64[(v >> 12) & 63]; out[o++] = B64[(v >> 6) & 63]; out[o++] = '='; } out[o] = '\0'; return out; } int util_random(void *out, size_t n) { unsigned char *p = out; size_t got = 0; while (got < n) { ssize_t r = getrandom(p + got, n - got, 0); if (r < 0) { if (errno == EINTR) continue; break; } got += (size_t)r; } if (got == n) return 0; int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; got = 0; while (got < n) { ssize_t r = read(fd, p + got, n - got); if (r <= 0) { if (r < 0 && errno == EINTR) continue; close(fd); return -1; } got += (size_t)r; } close(fd); return 0; } char *util_random_id(const char *prefix, size_t nbytes) { unsigned char raw[64]; if (nbytes > sizeof raw) nbytes = sizeof raw; if (util_random(raw, nbytes) != 0) { fprintf(stderr, "fatal: no entropy source\n"); exit(1); } char *b = util_b64url(raw, nbytes); size_t plen = prefix ? strlen(prefix) : 0; char *out = xmalloc(plen + strlen(b) + 1); memcpy(out, prefix ? prefix : "", plen); strcpy(out + plen, b); free(b); return out; } int util_const_eq(const void *a, const void *b, size_t n) { const volatile unsigned char *x = a; const volatile unsigned char *y = b; unsigned char d = 0; for (size_t i = 0; i < n; i++) d |= (unsigned char)(x[i] ^ y[i]); return d == 0; } int64_t util_now(void) { return (int64_t)time(NULL); } void util_iso8601(int64_t t, char *buf, size_t n) { time_t tt = (time_t)t; struct tm tm; gmtime_r(&tt, &tm); strftime(buf, n, "%Y-%m-%dT%H:%M:%SZ", &tm); } char *util_str_trim(char *s) { while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') s++; size_t len = strlen(s); while (len > 0) { char c = s[len - 1]; if (c != ' ' && c != '\t' && c != '\r' && c != '\n') break; s[--len] = '\0'; } return s; } static int is_leap(int y) { return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; } int util_parse_iso_date(const char *s) { if (!s || strlen(s) != 10) return 0; if (s[4] != '-' || s[7] != '-') return 0; static const int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int y = 0, m = 0, d = 0; for (int i = 0; i < 10; i++) { if (i == 4 || i == 7) continue; if (s[i] < '0' || s[i] > '9') return 0; } y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0'); m = (s[5] - '0') * 10 + (s[6] - '0'); d = (s[8] - '0') * 10 + (s[9] - '0'); if (y < 1900 || y > 2200 || m < 1 || m > 12 || d < 1) return 0; int max = mdays[m - 1] + (m == 2 && is_leap(y) ? 1 : 0); return d <= max; } /* ------------------------------------------------------------------ */ /* base64 */ /* ------------------------------------------------------------------ */ static int b64_val(char c) { if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') return c - 'a' + 26; if (c >= '0' && c <= '9') return c - '0' + 52; if (c == '+') return 62; if (c == '/') return 63; return -1; } int util_b64_decode(const char *in, size_t in_len, unsigned char **out, size_t *out_len) { while (in_len > 0 && (in[in_len - 1] == '\n' || in[in_len - 1] == '\r' || in[in_len - 1] == ' ' || in[in_len - 1] == '\t')) in_len--; size_t i = 0; while (i < in_len && (in[i] == '\n' || in[i] == '\r' || in[i] == ' ' || in[i] == '\t')) i++; if (i >= in_len) return -1; size_t cap = (in_len - i) / 4 * 3 + 3; unsigned char *buf = xmalloc(cap); size_t o = 0; int pad = 0; for (; i < in_len; i += 4) { if (i + 1 > in_len - 1) break; int v[4]; for (int k = 0; k < 4 && i + (size_t)k < in_len; k++) { char c = in[i + (size_t)k]; if (c == '=') { pad++; v[k] = 0; } else { v[k] = b64_val(c); if (v[k] < 0) { free(buf); return -1; } if (pad) { free(buf); return -1; } } } if (pad > 2) { free(buf); return -1; } buf[o++] = (unsigned char)((v[0] << 2) | (v[1] >> 4)); if (pad < 2) buf[o++] = (unsigned char)(((v[1] & 15) << 4) | (v[2] >> 2)); if (pad < 1) buf[o++] = (unsigned char)(((v[2] & 3) << 6) | v[3]); } *out = buf; *out_len = o; return 0; } /* ------------------------------------------------------------------ */ /* dates */ /* ------------------------------------------------------------------ */ int util_date_parse(const char *s, int *y, int *m, int *d) { if (!util_parse_iso_date(s)) return -1; *y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0'); *m = (s[5] - '0') * 10 + (s[6] - '0'); *d = (s[8] - '0') * 10 + (s[9] - '0'); return 0; } void util_date_fmt(int y, int m, int d, char *buf, size_t n) { snprintf(buf, n, "%04d-%02d-%02d", y, m, d); } int util_days_in_month(int y, int m) { static const int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (m < 1 || m > 12) return 0; return mdays[m - 1] + (m == 2 && is_leap(y) ? 1 : 0); } void util_date_add_months(const char *date, int months, char *out, size_t n) { int y, m, d; if (util_date_parse(date, &y, &m, &d) != 0) { snprintf(out, n, "%s", date); return; } int total = y * 12 + (m - 1) + months; int ny = total / 12; int nm = total % 12 + 1; int max = util_days_in_month(ny, nm); if (d > max) d = max; util_date_fmt(ny, nm, d, out, n); } static int64_t days_from_civil(int y, int m, int d) { y -= m <= 2; int64_t era = (y >= 0 ? y : y - 399) / 400; unsigned yoe = (unsigned)(y - era * 400); unsigned doy = (unsigned)((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1); unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; return era * 146097 + (int64_t)doe - 719468; } static void civil_from_days(int64_t z, int *y, int *m, int *d) { z += 719468; int64_t era = (z >= 0 ? z : z - 146096) / 146097; unsigned doe = (unsigned)(z - era * 146097); unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; int yy = (int)(yoe + era * 400); unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); unsigned mp = (5 * doy + 2) / 153; *d = (int)(doy - (153 * mp + 2) / 5 + 1); *m = (int)(mp + (mp < 10 ? 3 : -9)); *y = yy + (*m <= 2); } void util_date_add_days(const char *date, int days, char *out, size_t n) { int y, m, d; if (util_date_parse(date, &y, &m, &d) != 0) { snprintf(out, n, "%s", date); return; } int64_t z = days_from_civil(y, m, d) + days; civil_from_days(z, &y, &m, &d); util_date_fmt(y, m, d, out, n); } int util_date_valid(const char *s) { return util_parse_iso_date(s); } /* ------------------------------------------------------------------ */ /* CP437 conversion (subset: Latin-1 letters used in SIE files) */ /* ------------------------------------------------------------------ */ /* Unicode codepoints for CP437 bytes 0x80..0xDF. Bytes 0xE0..0xFF are Greek/math symbols that do not occur in Swedish bookkeeping text and convert to '?'. */ static const uint16_t CP437_HIGH[0x60] = { 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, }; static size_t cp437_put_utf8(unsigned char *out, size_t o, uint16_t cp) { if (cp < 0x80) { out[o++] = (unsigned char)cp; } else if (cp < 0x800) { out[o++] = (unsigned char)(0xC0 | (cp >> 6)); out[o++] = (unsigned char)(0x80 | (cp & 0x3F)); } else { out[o++] = (unsigned char)(0xE0 | (cp >> 12)); out[o++] = (unsigned char)(0x80 | ((cp >> 6) & 0x3F)); out[o++] = (unsigned char)(0x80 | (cp & 0x3F)); } return o; } char *util_cp437_to_utf8(const unsigned char *in, size_t n) { char *out = xmalloc(n * 2 + 1); size_t o = 0; for (size_t i = 0; i < n; i++) { unsigned char b = in[i]; if (b < 0x80) { out[o++] = (char)b; } else if (b <= 0xDF) { o = cp437_put_utf8((unsigned char *)out, o, CP437_HIGH[b - 0x80]); } else { out[o++] = '?'; } } out[o] = '\0'; return out; } size_t util_utf8_to_cp437(const char *in, unsigned char *out, size_t out_sz) { size_t o = 0; const unsigned char *p = (const unsigned char *)in; while (*p && o < out_sz) { uint32_t cp; size_t len; if (*p < 0x80) { cp = *p; len = 1; } else if ((*p & 0xE0) == 0xC0) { cp = (uint32_t)(*p & 0x1F) << 6; if ((p[1] & 0xC0) != 0x80) break; cp |= p[1] & 0x3F; len = 2; } else if ((*p & 0xF0) == 0xE0) { cp = (uint32_t)(*p & 0x0F) << 12; if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) break; cp |= (uint32_t)(p[1] & 0x3F) << 6; cp |= p[2] & 0x3F; len = 3; } else { p++; out[o++] = '?'; continue; } if (cp < 0x80) { out[o++] = (unsigned char)cp; } else { unsigned char best = '?'; for (unsigned b = 0x80; b <= 0xDF; b++) { if (CP437_HIGH[b - 0x80] == cp) { best = (unsigned char)b; break; } } out[o++] = best; } p += len; } return o; }