mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 02:49:02 -04:00
457 lines
8.3 KiB
C
457 lines
8.3 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)
|
|
{
|
|
unsigned mac = 0;
|
|
|
|
/* skip initial delimiters */
|
|
str += strspn(str, delimiters);
|
|
|
|
while (*str != 0 && mac < split_max) {
|
|
/* start of the token */
|
|
split_map[mac] = str;
|
|
++mac;
|
|
|
|
/* find the first delimiter or the end of the string */
|
|
str += strcspn(str, delimiters);
|
|
|
|
/* put the final terminator if missing */
|
|
if (*str != 0)
|
|
*str++ = 0;
|
|
|
|
/* skip trailing delimiters */
|
|
str += strspn(str, delimiters);
|
|
}
|
|
|
|
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 == '\\' && 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;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* 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;
|
|
}
|
|
|
|
int wnmatch_sub(const char* p, const char* t, int match_sub)
|
|
{
|
|
char p1 = 0; /* previous char */
|
|
while (*p) {
|
|
char p0 = *p;
|
|
switch (p0) {
|
|
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 (p1 == '/' || *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 == '/' && (p1 == 0 || p1 == '/')) {
|
|
/* 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 *t != 0;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
p1 = p0;
|
|
}
|
|
|
|
/* if we match sub directory */
|
|
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;
|
|
}
|
|
}
|
|
|