mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-25 18:40:19 -04:00
628 lines
11 KiB
C
628 lines
11 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2025 Andrea Mazzoleni
|
|
|
|
#include "os/portable.h"
|
|
|
|
#include "str.h"
|
|
#include "support.h"
|
|
|
|
/****************************************************************************/
|
|
/* string */
|
|
|
|
char* strpolish(char* s)
|
|
{
|
|
char* i = s;
|
|
|
|
while (*i) {
|
|
if (isspace((unsigned char)*i) || !isprint((unsigned char)*i))
|
|
*i = ' ';
|
|
++i;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
unsigned strsplit(char** split_map, unsigned split_max, char* str, const char* delimiters, const char* trim, int trim_empty)
|
|
{
|
|
unsigned mac = 0;
|
|
|
|
if (*str == 0)
|
|
return 0;
|
|
|
|
while (mac < split_max) {
|
|
char* begin = str;
|
|
|
|
str += strcspn(str, delimiters);
|
|
|
|
char* end = str;
|
|
|
|
int has_next = *str != 0;
|
|
if (has_next) {
|
|
*str = 0;
|
|
++str;
|
|
}
|
|
|
|
if (trim != 0) {
|
|
begin += strspn(begin, trim);
|
|
|
|
while (end > begin && strchr(trim, end[-1]) != 0) {
|
|
--end;
|
|
*end = 0;
|
|
}
|
|
}
|
|
|
|
if (!trim_empty || *begin != 0) {
|
|
split_map[mac] = begin;
|
|
++mac;
|
|
}
|
|
|
|
if (!has_next)
|
|
break;
|
|
}
|
|
|
|
return mac;
|
|
}
|
|
|
|
unsigned argsplit(const char** split_map, unsigned split_max, char* str)
|
|
{
|
|
unsigned mac = 0;
|
|
|
|
/* skip leading whitespace */
|
|
while (*str != 0 && isspace((unsigned char)*str))
|
|
++str;
|
|
|
|
while (*str != 0 && mac < split_max) {
|
|
char* dst = str;
|
|
split_map[mac++] = dst;
|
|
|
|
#ifdef _WIN32
|
|
/*
|
|
* cmd.exe rules:
|
|
* Unquoted: '^' escapes next char; '"' opens a quoted block.
|
|
* Quoted: '"' closes the block; '^' is an ordinary character.
|
|
*/
|
|
int quoted = 0;
|
|
|
|
while (*str != 0 && (quoted || !isspace((unsigned char)*str))) {
|
|
if (quoted) {
|
|
if (*str == '"') {
|
|
quoted = 0;
|
|
++str;
|
|
} else {
|
|
*dst++ = *str++;
|
|
}
|
|
} else {
|
|
if (*str == '^' && *(str + 1) != 0) {
|
|
++str;
|
|
*dst++ = *str++;
|
|
} else if (*str == '"') {
|
|
quoted = 1;
|
|
++str;
|
|
} else {
|
|
*dst++ = *str++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
/*
|
|
* bash rules:
|
|
* Single-quoted block: everything literal, no exceptions.
|
|
* Double-quoted block: '\' only escapes $, `, ", '\', newline;
|
|
* other backslashes are copied verbatim.
|
|
* Unquoted '\': always escapes the immediately following char.
|
|
*/
|
|
|
|
/* set of characters that '\' can escape inside double quotes */
|
|
static const char dq_escapable[] = "$`\"\\\n";
|
|
|
|
while (*str != 0 && !isspace((unsigned char)*str)) {
|
|
|
|
if (*str == '\'') {
|
|
/* single-quoted block: truly everything is literal */
|
|
++str;
|
|
while (*str != 0 && *str != '\'')
|
|
*dst++ = *str++;
|
|
if (*str == '\'')
|
|
++str;
|
|
|
|
} else if (*str == '"') {
|
|
/* double-quoted block */
|
|
++str;
|
|
while (*str != 0 && *str != '"') {
|
|
if (*str == '\\' && *(str + 1) != 0 && strchr(dq_escapable, *(str + 1))) {
|
|
++str;
|
|
}
|
|
*dst++ = *str++;
|
|
}
|
|
if (*str == '"')
|
|
++str;
|
|
|
|
} else if (*str == '\\') {
|
|
/* unquoted backslash: next character always literal */
|
|
++str;
|
|
if (*str != 0)
|
|
*dst++ = *str++;
|
|
|
|
} else {
|
|
*dst++ = *str++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (*str != 0)
|
|
++str; /* step past the delimiter */
|
|
|
|
/* skip whitespace before the next token */
|
|
while (*str != 0 && isspace((unsigned char)*str))
|
|
++str;
|
|
|
|
/* null-terminate after str has already moved on */
|
|
*dst = 0;
|
|
}
|
|
|
|
return mac;
|
|
}
|
|
|
|
char* strtrim(char* str)
|
|
{
|
|
char* begin;
|
|
char* end;
|
|
|
|
begin = str;
|
|
while (begin[0] && isspace((unsigned char)begin[0]))
|
|
++begin;
|
|
|
|
end = begin + strlen(begin);
|
|
while (end > begin && isspace((unsigned char)end[-1]))
|
|
--end;
|
|
|
|
end[0] = 0;
|
|
|
|
if (begin != str)
|
|
memmove(str, begin, end - begin + 1);
|
|
|
|
return str;
|
|
}
|
|
|
|
char* strlwr(char* str)
|
|
{
|
|
char* s = str;
|
|
|
|
while (*s) {
|
|
*s = tolower((unsigned char)*s);
|
|
++s;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
char* worddigitstr(const char* haystack, const char* needle)
|
|
{
|
|
size_t len = strlen(needle);
|
|
const char* s;
|
|
|
|
if (len == 0)
|
|
return NULL;
|
|
|
|
for (s = haystack; (s = strstr(s, needle)) != NULL; ++s) {
|
|
|
|
/* left boundary */
|
|
if (s == haystack || isspace((unsigned char)s[-1]) || isdigit((unsigned char)s[-1])) {
|
|
/* right boundary */
|
|
if (s[len] == 0 || isspace((unsigned char)s[len]) || isdigit((unsigned char)s[len])) {
|
|
return (char*)s;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int strtoi(const char* nptr, char** endptr, int base)
|
|
{
|
|
long val;
|
|
int save_errno = errno;
|
|
|
|
errno = 0;
|
|
val = strtol(nptr, endptr, base);
|
|
|
|
if (errno == ERANGE) {
|
|
return val > 0 ? INT_MAX : INT_MIN;
|
|
}
|
|
|
|
if (val > INT_MAX) {
|
|
errno = ERANGE;
|
|
return INT_MAX;
|
|
}
|
|
|
|
if (val < INT_MIN) {
|
|
errno = ERANGE;
|
|
return INT_MIN;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
return (int)val;
|
|
}
|
|
|
|
unsigned strtou(const char* nptr, char** endptr, int base)
|
|
{
|
|
const char* p = nptr;
|
|
char* end;
|
|
unsigned long val;
|
|
int neg = 0;
|
|
int save_errno = errno;
|
|
|
|
while (isspace((unsigned char)*p)) {
|
|
++p;
|
|
}
|
|
|
|
if (*p == '-') {
|
|
neg = 1;
|
|
++p;
|
|
} else if (*p == '+') {
|
|
++p;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtoul(p, &end, base);
|
|
|
|
if (end == p) {
|
|
/* no conversion was performed */
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
errno = save_errno;
|
|
return 0;
|
|
}
|
|
|
|
if (endptr) {
|
|
*endptr = end;
|
|
}
|
|
|
|
if (errno == ERANGE || val > UINT_MAX) {
|
|
errno = ERANGE;
|
|
return UINT_MAX;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
if (neg) {
|
|
return (unsigned)-val;
|
|
}
|
|
|
|
return (unsigned)val;
|
|
}
|
|
|
|
unsigned strton(const char* nptr, char** endptr, int base)
|
|
{
|
|
const char* p = nptr;
|
|
unsigned val;
|
|
int save_errno = errno;
|
|
|
|
while (isspace((unsigned char)*p)) {
|
|
++p;
|
|
}
|
|
|
|
if (*p == '-') {
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
errno = ERANGE;
|
|
return (unsigned)-1;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtou(nptr, endptr, base);
|
|
|
|
if (errno == ERANGE) {
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
return (unsigned)-1;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
unsigned long strtonl(const char* nptr, char** endptr, int base)
|
|
{
|
|
const char* p = nptr;
|
|
unsigned long val;
|
|
int save_errno = errno;
|
|
|
|
while (isspace((unsigned char)*p)) {
|
|
++p;
|
|
}
|
|
|
|
if (*p == '-') {
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
errno = ERANGE;
|
|
return (unsigned long)-1;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtoul(nptr, endptr, base);
|
|
|
|
if (errno == ERANGE) {
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
return (unsigned long)-1;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
unsigned long long strtonll(const char* nptr, char** endptr, int base)
|
|
{
|
|
const char* p = nptr;
|
|
unsigned long long val;
|
|
int save_errno = errno;
|
|
|
|
while (isspace((unsigned char)*p)) {
|
|
++p;
|
|
}
|
|
|
|
if (*p == '-') {
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
errno = ERANGE;
|
|
return (unsigned long long)-1;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtoull(nptr, endptr, base);
|
|
|
|
if (errno == ERANGE) {
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
return (unsigned long long)-1;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* wnmatch */
|
|
|
|
/**
|
|
* Helper function for case-insensitive character comparison
|
|
*/
|
|
static inline int char_match(char p, char t)
|
|
{
|
|
#ifdef WIN32
|
|
return tolower((unsigned char)p) == tolower((unsigned char)t);
|
|
#else
|
|
return p == t;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Match character class [...]
|
|
* Return 0 if NOT matched
|
|
*/
|
|
static const char* match_class(const char* p, char t)
|
|
{
|
|
int negate = 0;
|
|
int matched = 0;
|
|
|
|
if (*p == '!' || *p == '^') {
|
|
negate = 1;
|
|
++p;
|
|
}
|
|
|
|
while (*p && *p != ']') {
|
|
if (p[1] == '-' && p[2] != ']' && p[2] != 0) {
|
|
/* range [a-z] */
|
|
unsigned char start = (unsigned char)*p;
|
|
unsigned char end = (unsigned char)p[2];
|
|
unsigned char c = (unsigned char)t;
|
|
#ifdef WIN32
|
|
start = tolower(start);
|
|
end = tolower(end);
|
|
c = tolower(c);
|
|
#endif
|
|
if (c >= start && c <= end)
|
|
matched = 1;
|
|
p += 3;
|
|
} else {
|
|
/* single character */
|
|
if (char_match(*p, t))
|
|
matched = 1;
|
|
++p;
|
|
}
|
|
}
|
|
|
|
if (*p == ']')
|
|
++p;
|
|
|
|
if (negate)
|
|
matched = !matched;
|
|
if (!matched)
|
|
return 0;
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Helper function to check end of match condition
|
|
*/
|
|
static inline int wnmatch_end(const char* t, int match_sub)
|
|
{
|
|
if (match_sub) {
|
|
/* match successfully only if we are at a directory border */
|
|
return *t != '/';
|
|
} else {
|
|
/* match successfully if we've consumed all text */
|
|
return *t != 0;
|
|
}
|
|
}
|
|
|
|
static inline int is_wild_escape(char c)
|
|
{
|
|
return c == '*'
|
|
|| c == '?'
|
|
|| c == '['
|
|
|| c == ']'
|
|
#ifdef _WIN32
|
|
|| c == '^';
|
|
#else
|
|
|| c == '\\';
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Match with the previous pattern character provided in prev.
|
|
*
|
|
* An initial '**' needs to know if it was preceded by '/' to distinguish
|
|
* between multi-directory recursive matching (like /##.ext) and single-directory
|
|
* matching (like ##.ext).
|
|
*/
|
|
int wnmatch_sub_prev(const char* p, const char* t, int match_sub, char prev)
|
|
{
|
|
while (*p) {
|
|
char p0 = *p;
|
|
switch (p0) {
|
|
#ifdef _WIN32
|
|
case '^' :
|
|
#else
|
|
case '\\' :
|
|
#endif
|
|
/*
|
|
* Escape wildcard metacharacters.
|
|
*
|
|
* If the following character is not escapable, the
|
|
* escape character itself is matched literally.
|
|
*/
|
|
if (is_wild_escape(p[1]))
|
|
++p;
|
|
|
|
if (*t == 0 || !char_match(*p, *t))
|
|
return 1;
|
|
|
|
++p;
|
|
++t;
|
|
break;
|
|
case '?' :
|
|
/* ? matches any single character except / */
|
|
if (*t == 0 || *t == '/')
|
|
return 1;
|
|
++p;
|
|
++t;
|
|
break;
|
|
case '*' :
|
|
/* check for ** */
|
|
if (p[1] == '*') {
|
|
/* skip the ** */
|
|
p += 2;
|
|
|
|
/* munge all * */
|
|
while (*p == '*')
|
|
++p;
|
|
|
|
/* if its not near a slash, it's like a single * */
|
|
if (prev == '/' || *p == '/') {
|
|
/* a ** at end matches everything */
|
|
if (*p == 0)
|
|
return 0;
|
|
|
|
/*
|
|
* In between slashes matches to nothing
|
|
*
|
|
* Check for /##/ or ^##/ (^ start of string)
|
|
*
|
|
* This is required for:
|
|
* "/##/file.txt" matching "/file.txt"
|
|
* "##/file.txt" matching "file.txt"
|
|
* "x##/file.txt" NOT matching "xfile.txt"
|
|
*/
|
|
if (*p == '/' && (prev == 0 || prev == '/')) {
|
|
/* try reducing to nothing */
|
|
if (wnmatch_sub(p + 1, t, match_sub) == 0)
|
|
return 0;
|
|
/* otherwise / should match in the text */
|
|
}
|
|
|
|
/* try matching with 0 or more characters */
|
|
while (*t) {
|
|
if (wnmatch_sub(p, t, match_sub) == 0)
|
|
return 0;
|
|
++t;
|
|
}
|
|
|
|
/* try matching at the end */
|
|
return wnmatch_sub(p, t, match_sub);
|
|
}
|
|
} else {
|
|
/* skip the * */
|
|
++p;
|
|
}
|
|
|
|
/* a * at end matches rest of segment */
|
|
if (*p == 0) {
|
|
while (*t && *t != '/')
|
|
++t;
|
|
return wnmatch_end(t, match_sub);
|
|
}
|
|
|
|
/* try matching with 0 or more characters */
|
|
while (*t && *t != '/') {
|
|
if (wnmatch_sub(p, t, match_sub) == 0)
|
|
return 0;
|
|
++t;
|
|
}
|
|
|
|
/* try matching at the end */
|
|
return wnmatch_sub(p, t, match_sub);
|
|
case '[' :
|
|
/* character class */
|
|
if (*t == 0 || *t == '/')
|
|
return 1;
|
|
|
|
p = match_class(p + 1, *t);
|
|
if (!p)
|
|
return 1;
|
|
|
|
++t;
|
|
break;
|
|
default :
|
|
/* literal character */
|
|
if (*t == 0 || !char_match(*p, *t))
|
|
return 1;
|
|
++p;
|
|
++t;
|
|
break;
|
|
}
|
|
prev = p0;
|
|
}
|
|
|
|
return wnmatch_end(t, match_sub);
|
|
}
|
|
|
|
int wnmatch_sub(const char* p, const char* t, int match_sub)
|
|
{
|
|
return wnmatch_sub_prev(p, t, match_sub, 0);
|
|
}
|
|
|