diff options
Diffstat (limited to 'src/string.c')
-rw-r--r-- | src/string.c | 382 |
1 files changed, 354 insertions, 28 deletions
diff --git a/src/string.c b/src/string.c index 354a409..ae19271 100644 --- a/src/string.c +++ b/src/string.c @@ -8,6 +8,8 @@ * either version 2.1 or (at your option) any later version. */ #include <errno.h> +#include <limits.h> +#include <math.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -25,6 +27,7 @@ * all others pass through */ enum HX_quote_selector { + HXQUOTE_ALWAYS, HXQUOTE_ACCEPT, HXQUOTE_REJECT, }; @@ -40,11 +43,6 @@ struct HX_quote_rule { static const char HX_hexenc[16] = "0123456789ABCDEF"; -static __inline__ unsigned int min_uint(unsigned int a, unsigned int b) -{ - return (a < b) ? a : b; -} - EXPORT_SYMBOL char *HX_basename(const char *s) { const char *p; @@ -214,6 +212,8 @@ EXPORT_SYMBOL char **HX_split(const char *str, const char *delim, *cp = max; ret = malloc(sizeof(char *) * (*cp + 1)); + if (ret == nullptr) + return nullptr; ret[*cp] = NULL; { @@ -381,20 +381,48 @@ EXPORT_SYMBOL char *HX_stpltrim(const char *p) return const_cast1(char *, p); } +/** + * Helper for substr() function for dealing with negative off/len values + * @z: total length of string + * @offset: n>=0 specifies offset from the start, + * n<0 specifies offset from the end + * @len: "length"; n>=0 specifies length to copy (from @offset), + * n<0 specifies the byte relative to the end at which to stop + * + * @abstart: (result) absolute start + * @retval: (result) absolute length to copy (from *@abstart) + */ +size_t HX_substr_helper(size_t z, long offset, long len, size_t *start) +{ + if (offset >= 0) + *start = offset; + else if (offset == LONG_MIN) + *start = z + -static_cast(size_t, LONG_MIN); + else + *start = z >= static_cast(unsigned long, -offset) ? z + offset : z; + + size_t end; + if (len >= 0) + end = *start < SIZE_MAX - len ? *start + len : SIZE_MAX; + else if (len == LONG_MIN) + end = z + -static_cast(unsigned long, LONG_MIN); + else + end = z >= static_cast(unsigned long, -len) ? z + len : 0; + if (end > z) + end = z; + return end > *start ? end - *start : 0; +} + /* supports negative offsets like scripting languages */ EXPORT_SYMBOL char *HX_strmid(const char *expr, long offset, long length) { - char *buffer; - - if (offset < 0) - offset = strlen(expr) + offset; - if (length < 0) - length = strlen(expr) - offset + length; - if ((buffer = malloc(length + 1)) == NULL) + size_t start = 0, tocopy = HX_substr_helper(strlen(expr), offset, length, &start); + char *buffer = malloc(tocopy + 1); + if (buffer == nullptr) return NULL; - - expr += offset; - return HX_strlcpy(buffer, expr, length + 1); + memcpy(buffer, &expr[start], tocopy); + buffer[tocopy] = '\0'; + return buffer; } EXPORT_SYMBOL char *HX_strndup(const char *src, size_t size) @@ -605,14 +633,16 @@ HX_quote_sqlbackslash(char *dest, const char *src, const char *trm) * Encode @src into BASE-64 according to RFC 4648 and write result to @dest, * which must be of appropriate size, plus one for a trailing NUL. */ -static char *HX_quote_base64(char *d, const char *s) +static char *HX_quote_base64(char *d, const char *s, char x1, char x2) { - static const char a[] = + char a[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz0123456789+/"; + "abcdefghijklmnopqrstuvwxyz0123456789??"; size_t len = strlen(s); char *ret = d; + a[62] = x1; + a[63] = x2; while (len > 0) { if (len >= 3) { len -= 3; @@ -761,6 +791,8 @@ static size_t HX_quoted_size(const char *s, unsigned int type) case HXQUOTE_LDAPRDN: return HX_qsize_bsr(s, HX_quote_rules[type].chars, 2); case HXQUOTE_BASE64: + case HXQUOTE_BASE64URL: + case HXQUOTE_BASE64IMAP: return (strlen(s) + 2) / 3 * 4; case HXQUOTE_URIENC: return HX_qsize_bsa(s, HX_quote_rules[type].chars, 2); @@ -772,7 +804,7 @@ static size_t HX_quoted_size(const char *s, unsigned int type) EXPORT_SYMBOL char *HX_strquote(const char *src, unsigned int type, char **free_me) { - const struct HX_quote_rule *rule; + const struct HX_quote_rule *rule = nullptr; bool do_quote; char *tmp; @@ -781,15 +813,19 @@ EXPORT_SYMBOL char *HX_strquote(const char *src, unsigned int type, return NULL; } /* If quote_chars is NULL, it is clear all chars are to be encoded. */ - rule = &HX_quote_rules[type]; - if (type >= ARRAY_SIZE(HX_quote_rules) || rule->chars == NULL) + if (type >= ARRAY_SIZE(HX_quote_rules)) { do_quote = true; - else if (rule->selector == HXQUOTE_REJECT) - do_quote = strpbrk(src, rule->chars) != NULL; - else if (rule->selector == HXQUOTE_ACCEPT) - do_quote = HX_strchr2(src, rule->chars) != NULL; - else - do_quote = false; + } else { + rule = &HX_quote_rules[type]; + if (rule->selector == HXQUOTE_ALWAYS) + do_quote = true; + else if (rule->selector == HXQUOTE_REJECT) + do_quote = strpbrk(src, rule->chars) != NULL; + else if (rule->selector == HXQUOTE_ACCEPT) + do_quote = HX_strchr2(src, rule->chars) != NULL; + else + do_quote = false; + } /* * free_me == NULL implies that we always allocate, even if * there is nothing to quote. @@ -819,7 +855,11 @@ EXPORT_SYMBOL char *HX_strquote(const char *src, unsigned int type, case HXQUOTE_LDAPRDN: return HX_quote_ldap(*free_me, src, rule->chars); case HXQUOTE_BASE64: - return HX_quote_base64(*free_me, src); + return HX_quote_base64(*free_me, src, '+', '/'); + case HXQUOTE_BASE64URL: + return HX_quote_base64(*free_me, src, '-', '_'); + case HXQUOTE_BASE64IMAP: + return HX_quote_base64(*free_me, src, '+', ','); case HXQUOTE_URIENC: return HX_quote_urlenc(*free_me, src); case HXQUOTE_SQLSQUOTE: @@ -836,3 +876,289 @@ EXPORT_SYMBOL char *HX_strupper(char *orig) *expr = HX_toupper(*expr); return orig; } + +EXPORT_SYMBOL char *HX_unit_size(char *buf, size_t bufsize, + unsigned long long size, unsigned int divisor, unsigned int cutoff) +{ + static const char unit_names[] = "\0kMGTPEZYRQ"; + unsigned int unit_idx = 0; + if (divisor == 0) + divisor = 1000; + if (cutoff == 0) { + cutoff = 10000; + if (cutoff < divisor) + cutoff = divisor; + } + while (unit_idx < ARRAY_SIZE(unit_names) - 1 && size >= cutoff) { + ++unit_idx; + size /= divisor; + } + snprintf(buf, bufsize, "%llu%.1s", size, &unit_names[unit_idx]); + return buf; +} + +static inline unsigned long long p_90(unsigned long long x) +{ + /* Perform x*9/10, but without the risk of overflow. */ + return x - x / 10 - !!(x % 10); +} + +EXPORT_SYMBOL char *HX_unit_size_cu(char *buf, size_t bufsize, + unsigned long long orig_size, unsigned int divisor) +{ + /* No floating point. Take that, coreutils! */ + static const char unit_names[] = "\0kMGTPEZYRQ"; + unsigned int unit_idx = 0, last_rem = 0; + unsigned long long size = orig_size, gpow = 1, grand_rem; + if (divisor == 0) + divisor = 1000; + + while (unit_idx < ARRAY_SIZE(unit_names) - 1 && size >= divisor) { + ++unit_idx; + last_rem = size % divisor; + size /= divisor; + gpow *= divisor; + } + if (unit_idx == 0) { + snprintf(buf, bufsize, "%llu%.1s", size, &unit_names[unit_idx]); + return buf; + } + grand_rem = orig_size - size * gpow; + if (grand_rem != 0) { + if (grand_rem > p_90(gpow)) { + ++size; + last_rem = 0; + } else { + last_rem *= 10; + last_rem /= divisor; + ++last_rem; + if (last_rem == 10 || (size >= 10 && last_rem > 0)) { + ++size; + last_rem = 0; + } + } + if (unit_idx < ARRAY_SIZE(unit_names) - 1 && size == divisor) { + /* ++size from above may brought size to @divisor again */ + ++unit_idx; + size /= divisor; + } + } + if (size >= 10 && last_rem == 0) + snprintf(buf, bufsize, "%llu%.1s", size, &unit_names[unit_idx]); + else + snprintf(buf, bufsize, "%llu.%01u%.1s", size, last_rem, &unit_names[unit_idx]); + return buf; +} + +static unsigned int suffix_power(char u) +{ + switch (HX_toupper(u)) { + case 'K': return 1; + case 'M': return 2; + case 'G': return 3; + case 'T': return 4; + case 'P': return 5; + case 'E': return 6; + case 'Z': return 7; + case 'Y': return 8; + case 'R': return 9; + case 'Q': return 10; + default: return 0; + } +} + +EXPORT_SYMBOL double HX_strtod_unit(const char *s, char **out_end, unsigned int exponent) +{ + char *end; + double q; + + while (HX_isspace(*s)) + ++s; + q = strtod(s, &end); + if (exponent == 0) + exponent = 1000; + if (end == s) { + if (out_end != nullptr) + *out_end = end; + return q; + } + while (HX_isspace(*end)) + ++end; + unsigned int pwr = suffix_power(*end); + if (pwr == 0) { + if (out_end != nullptr) + *out_end = const_cast(char *, end); + return q; + } + if (out_end != nullptr) + *out_end = const_cast(char *, end + 1); + return q * pow(exponent, pwr); +} + +EXPORT_SYMBOL unsigned long long HX_strtoull_unit(const char *s, + char **out_end, unsigned int exponent) +{ + char *end; + unsigned long long ipart; + unsigned int pwr = 0; + + while (HX_isspace(*s)) + ++s; + ipart = strtoull(s, &end, 10); + if (*end == '.') { + double q = HX_strtod_unit(s, out_end, exponent); + bool lo_ok = q >= nextafter(-static_cast(double, ULLONG_MAX), 0); + bool hi_ok = q <= nextafter(static_cast(double, ULLONG_MAX), 0); + if (!hi_ok || !lo_ok) + return ULLONG_MAX; + return q; + } + if (exponent == 0) + exponent = 1000; + while (HX_isspace(*end)) + ++end; + pwr = suffix_power(*end); + if (pwr == 0) { + if (out_end != nullptr) + *out_end = end; + return ipart; + } + if (out_end != nullptr) + *out_end = const_cast(char *, end + 1); + while (pwr-- > 0) { + if (ipart >= ULLONG_MAX / exponent) { + errno = ERANGE; + return ULLONG_MAX; + } + ipart *= exponent; + } + return ipart; +} + +#define SECONDS_PER_YEAR 31557600 +#define SECONDS_PER_MONTH 2629800 + +static const struct { + const char name[8]; + unsigned int len; + uint32_t mult; +} time_multiplier[] = { + {"seconds", 7, 1}, + {"second", 6, 1}, + {"sec", 3, 1}, + {"s", 1, 1}, + {"minutes", 7, 60}, + {"minute", 6, 60}, + {"min", 3, 60}, + {"hours", 5, 3600}, + {"hour", 4, 3600}, + {"h", 1, 3600}, + {"days", 4, 86400}, + {"day", 3, 86400}, + {"d", 1, 86400}, + {"weeks", 5, 604800}, + {"week", 4, 604800}, + {"months", 6, SECONDS_PER_MONTH}, + {"month", 5, SECONDS_PER_MONTH}, + {"years", 5, SECONDS_PER_YEAR}, + {"year", 4, SECONDS_PER_YEAR}, + {"y", 1, SECONDS_PER_YEAR}, +}; + +EXPORT_SYMBOL unsigned long long HX_strtoull_sec(const char *s, char **out_end) +{ + unsigned long long seconds = 0; + + while (*s != '\0') { + while (HX_isspace(*s)) + ++s; + if (*s == '-') { + break; + } + char *end = nullptr; + unsigned long long num = strtoull(s, &end, 10); + if (end == s) + break; + s = end; + while (HX_isspace(*s)) + ++s; + if (!HX_isalpha(*s)) { + seconds += num; + continue; + } + unsigned int i; + for (i = 0; i < ARRAY_SIZE(time_multiplier); ++i) + if (strncmp(s, time_multiplier[i].name, + time_multiplier[i].len) == 0 && + !HX_isalpha(s[time_multiplier[i].len])) + break; + if (i == ARRAY_SIZE(time_multiplier)) + break; + seconds += num * time_multiplier[i].mult; + s += time_multiplier[i].len; + } + if (out_end != nullptr) + *out_end = const_cast(char *, s); + return seconds; +} + +EXPORT_SYMBOL char *HX_unit_seconds(char *out, size_t outsize, + unsigned long long secs, unsigned int flags) +{ + unsigned long years = 0, months = 0, weeks = 0, days, hours, mins; + char buf[HXSIZEOF_Z64+4]; + if (flags & HXUNIT_YEARS) { + years = secs / SECONDS_PER_YEAR; + secs %= SECONDS_PER_YEAR; + } + if (flags & HXUNIT_MONTHS) { + months = secs / SECONDS_PER_MONTH; + secs %= SECONDS_PER_MONTH; + } + if (flags & HXUNIT_WEEKS) { + weeks = secs / 604800; + secs %= 604800; + } + days = secs / 86400; + secs %= 86400; + hours = secs / 3600; + secs %= 3600; + mins = secs / 60; + secs %= 60; + *out = '\0'; + if (years > 0) { + snprintf(buf, ARRAY_SIZE(buf), "%luy", years); + HX_strlcat(out, buf, outsize); + } + if (months == 1) { + HX_strlcat(out, "1month", outsize); + } else if (months > 0) { + snprintf(buf, ARRAY_SIZE(buf), "%lumonths", months); + HX_strlcat(out, buf, outsize); + } + if (weeks == 1) { + HX_strlcat(out, "1week", outsize); + } else if (weeks > 0) { + snprintf(buf, ARRAY_SIZE(buf), "%luweeks", weeks); + HX_strlcat(out, buf, outsize); + } + if (days > 0) { + snprintf(buf, ARRAY_SIZE(buf), "%lud", days); + HX_strlcat(out, buf, outsize); + } + if (hours > 0) { + snprintf(buf, ARRAY_SIZE(buf), "%luh", hours); + HX_strlcat(out, buf, outsize); + } + if (mins > 0) { + snprintf(buf, ARRAY_SIZE(buf), "%lumin", mins); + HX_strlcat(out, buf, outsize); + } + if (secs > 0 || + (years == 0 && months == 0 && weeks == 0 && + days == 0 && hours == 0 && mins == 0)) { + snprintf(buf, ARRAY_SIZE(buf), "%llus", secs); + HX_strlcat(out, buf, outsize); + } + return out; +} |