mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Every file the core touches goes through libretro-common's filestream, path and dirent layers, taken from RetroArch: vfs_hybrid opens local paths with libretro-common's own implementation and hands URI paths (and, on Android, anything a local open cannot reach) to the frontend's VFS. File::IOFile and File::DirectIOFile sit on RFILE, and FileUtil, FileSearch and the directory walks on path_stat, path_mkdir and retro_opendir. Dolphin's fopen, POSIX and Win32 file code, std::filesystem and std::fstream are gone, with the dual VFS paths and the core option that chose between them. Files opened for reading alone are memory-mapped where the platform can: reads copy straight out of the page cache, DirectIOFile's offset reads on one mapped object are thread safe, and GetMappedPtr(), View() and Prefetch() lend and warm without a copy. Text files are read a line at a time through File::LineReader, lent from the mapping or a window refilled a block at a time, never a byte at a time; JSON is parsed straight from the mapping. Ini files and the cheat file are built in memory and written once, an ini only when it changed and then atomically.
1007 lines
29 KiB
C
1007 lines
29 KiB
C
/* Copyright (C) 2010-2020 The RetroArch team
|
|
*
|
|
* ---------------------------------------------------------------------------------------
|
|
* The following license statement only applies to this file (stdstring.c).
|
|
* ---------------------------------------------------------------------------------------
|
|
*
|
|
* Permission is hereby granted, free of charge,
|
|
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <retro_posix_source.h>
|
|
|
|
#include <stdint.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#include <compat/strl.h>
|
|
#include <string/stdstring.h>
|
|
#include <encodings/utf.h>
|
|
|
|
const uint8_t lr_char_props[256] = {
|
|
/*x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x00,0x00, /* 0x */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 1x */
|
|
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 2x !"#$%&'()*+,-./ */
|
|
0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x00,0x00,0x00,0x00,0x00,0x00, /* 3x 0123456789:;<=>? */
|
|
0x00,0x23,0x23,0x23,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, /* 4x @ABCDEFGHIJKLMNO */
|
|
0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x08, /* 5x PQRSTUVWXYZ[\]^_ */
|
|
0x00,0x25,0x25,0x25,0x25,0x25,0x25,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, /* 6x `abcdefghijklmno */
|
|
0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00, /* 7x pqrstuvwxyz{|}~ */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8x */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 9x */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ax */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Bx */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Cx */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Dx */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ex */
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Fx */
|
|
};
|
|
|
|
char *string_to_upper(char *s)
|
|
{
|
|
char *cs = (char *)s;
|
|
for ( ; *cs != '\0'; cs++)
|
|
*cs = toupper((unsigned char)*cs);
|
|
return s;
|
|
}
|
|
|
|
char *string_to_lower(char *s)
|
|
{
|
|
char *cs = (char *)s;
|
|
for ( ; *cs != '\0'; cs++)
|
|
*cs = tolower((unsigned char)*cs);
|
|
return s;
|
|
}
|
|
|
|
char *string_ucwords(char *s)
|
|
{
|
|
char *cs = (char *)s;
|
|
|
|
if (!s || *s == '\0')
|
|
return s;
|
|
|
|
for ( ; *cs != '\0'; cs++)
|
|
{
|
|
if (*cs == ' ' && *(cs + 1) != '\0')
|
|
*(cs+1) = toupper((unsigned char)*(cs+1));
|
|
}
|
|
|
|
s[0] = toupper((unsigned char)s[0]);
|
|
return s;
|
|
}
|
|
|
|
char *string_replace_substring(
|
|
const char *in, size_t in_len,
|
|
const char *pattern, size_t pattern_len,
|
|
const char *replacement, size_t replacement_len)
|
|
{
|
|
size_t _len;
|
|
size_t numhits = 0;
|
|
const char *inat = NULL;
|
|
const char *inprev = NULL;
|
|
char *out = NULL;
|
|
char *outat = NULL;
|
|
|
|
/* if either pattern or replacement is NULL,
|
|
* duplicate in and let caller handle it. */
|
|
if (!pattern || !replacement)
|
|
return strdup(in);
|
|
|
|
inat = in;
|
|
|
|
while ((inat = strstr(inat, pattern)))
|
|
{
|
|
inat += pattern_len;
|
|
numhits++;
|
|
}
|
|
|
|
_len = in_len - pattern_len * numhits + replacement_len*numhits;
|
|
|
|
if (!(out = (char *)malloc(_len + 1)))
|
|
return NULL;
|
|
|
|
outat = out;
|
|
inat = in;
|
|
inprev = in;
|
|
|
|
while ((inat = strstr(inat, pattern)))
|
|
{
|
|
memcpy(outat, inprev, inat-inprev);
|
|
outat += inat-inprev;
|
|
memcpy(outat, replacement, replacement_len);
|
|
outat += replacement_len;
|
|
inat += pattern_len;
|
|
inprev = inat;
|
|
}
|
|
|
|
_len = (in + in_len) - inprev;
|
|
strlcpy(outat, inprev, _len + 1);
|
|
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* string_trim_whitespace_left:
|
|
*
|
|
* Remove leading whitespaces
|
|
**/
|
|
char *string_trim_whitespace_left(char *const s)
|
|
{
|
|
if (s && *s)
|
|
{
|
|
char *current = s;
|
|
while (*current && (*current == ' ' || *current == '\t' || *current == '\n' || *current == '\r' || *current == '\v' || *current == '\f'))
|
|
++current;
|
|
if (s != current)
|
|
{
|
|
size_t _len = strlen(current);
|
|
memmove(s, current, _len + 1);
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* string_trim_whitespace_right:
|
|
*
|
|
* Remove trailing whitespaces
|
|
**/
|
|
char *string_trim_whitespace_right(char *const s)
|
|
{
|
|
if (s && *s)
|
|
{
|
|
size_t _len = strlen(s);
|
|
char *current = s + _len - 1;
|
|
|
|
while (current != s && ISSPACE((unsigned char)*current))
|
|
{
|
|
--current;
|
|
--_len;
|
|
}
|
|
|
|
current[ISSPACE((unsigned char)*current) ? 0 : 1] = '\0';
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* string_trim_whitespace:
|
|
*
|
|
* Remove leading and trailing whitespaces
|
|
**/
|
|
char *string_trim_whitespace(char *const s)
|
|
{
|
|
string_trim_whitespace_right(s); /* order matters */
|
|
string_trim_whitespace_left(s);
|
|
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* word_wrap:
|
|
* @s : pointer to destination buffer.
|
|
* @len : size of destination buffer.
|
|
* @src : pointer to input string.
|
|
* @line_width : max number of characters per line.
|
|
* @wideglyph_width : not used, but is necessary to keep
|
|
* compatibility with word_wrap_wideglyph().
|
|
* @max_lines : max lines of destination string.
|
|
* 0 means no limit.
|
|
*
|
|
* Wraps string specified by @src to destination buffer
|
|
* specified by @s and @len.
|
|
* This function assumes that all glyphs in the string
|
|
* have an on-screen pixel width similar to that of
|
|
* regular Latin characters - i.e. it will not wrap
|
|
* correctly any text containing so-called 'wide' Unicode
|
|
* characters (e.g. CJK languages, emojis, etc.).
|
|
* @s and @src must NOT overlap: the copy is performed
|
|
* with strlcpy(), which has undefined behaviour for
|
|
* overlapping buffers (and aborts via __chk_fail_overlap
|
|
* with fortified libc on macOS).
|
|
**/
|
|
size_t word_wrap(
|
|
char *s, size_t len,
|
|
const char *src, size_t src_len,
|
|
int line_width, int wideglyph_width, unsigned max_lines)
|
|
{
|
|
char *last_space = NULL;
|
|
unsigned counter = 0;
|
|
unsigned space_counter = 0;
|
|
bool space_on_line = false;
|
|
unsigned lines = 1;
|
|
const char *src_end = src + src_len;
|
|
char *s_start = s;
|
|
size_t text_len = 0;
|
|
|
|
/* Prevent buffer overflow */
|
|
if (len < src_len + 1)
|
|
return 0;
|
|
|
|
/* Early return if src string length is less
|
|
* than line width */
|
|
if (src_len < (size_t)line_width)
|
|
return strlcpy(s, src, len);
|
|
|
|
/* Only whitespace changes; rewound spans already contain their text. */
|
|
text_len = strlcpy(s, src, len);
|
|
|
|
while (*src != '\0')
|
|
{
|
|
unsigned char_len = (unsigned char)*src < 0x80
|
|
? 1 : (unsigned)(utf8skip(src, 1) - src);
|
|
counter++;
|
|
|
|
if (*src == ' ')
|
|
{
|
|
last_space = s; /* Remember the location of the whitespace */
|
|
space_counter = counter;
|
|
space_on_line = true;
|
|
}
|
|
else if (*src == '\n')
|
|
{
|
|
/* If newlines embedded in the input,
|
|
* reset the index */
|
|
lines++;
|
|
counter = 0;
|
|
space_on_line = false;
|
|
|
|
/* Early return if remaining src string
|
|
* length is less than line width */
|
|
if (src_end - src <= line_width)
|
|
return text_len;
|
|
}
|
|
|
|
s += char_len;
|
|
src += char_len;
|
|
|
|
if (counter >= (unsigned)line_width)
|
|
{
|
|
unsigned scanned = counter;
|
|
counter = 0;
|
|
|
|
if (last_space && (max_lines == 0 || lines < max_lines))
|
|
{
|
|
const char *restart = src - (s - last_space - 1);
|
|
/* Replace nearest (previous) whitespace
|
|
* with newline character */
|
|
*last_space = '\n';
|
|
lines++;
|
|
|
|
if (space_on_line)
|
|
counter = scanned - space_counter;
|
|
else
|
|
{
|
|
/* A saved space may precede an embedded newline. */
|
|
src = restart;
|
|
s = last_space + 1;
|
|
}
|
|
last_space = NULL;
|
|
space_on_line = false;
|
|
|
|
/* Early return if remaining src string
|
|
* length is less than line width */
|
|
if (src_end - restart < line_width)
|
|
return text_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
*s = '\0';
|
|
return (size_t)(s - s_start);
|
|
}
|
|
|
|
/**
|
|
* word_wrap_wideglyph:
|
|
* @dst : pointer to destination buffer.
|
|
* @len : size of destination buffer.
|
|
* @src : pointer to input string.
|
|
* @line_width : max number of characters per line.
|
|
* @wideglyph_width : effective width of 'wide' Unicode glyphs.
|
|
* the value here is normalised relative to the
|
|
* typical on-screen pixel width of a regular
|
|
* Latin character:
|
|
* - a regular Latin character is defined to
|
|
* have an effective width of 100
|
|
* - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width)
|
|
* - e.g. if 'wide' Unicode characters in 'src'
|
|
* have an on-screen pixel width twice that of
|
|
* regular Latin characters, wideglyph_width
|
|
* would be 200
|
|
* @max_lines : max lines of destination string.
|
|
* 0 means no limit.
|
|
*
|
|
* Wraps string specified by @src to destination buffer
|
|
* specified by @dst and @len.
|
|
* This function assumes that all glyphs in the string
|
|
* are:
|
|
* - EITHER 'non-wide' Unicode glyphs, with an on-screen
|
|
* pixel width similar to that of regular Latin characters
|
|
* - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.)
|
|
* with an on-screen pixel width defined by @wideglyph_width
|
|
* Note that wrapping may occur in inappropriate locations
|
|
* if @src string contains 'wide' Unicode characters whose
|
|
* on-screen pixel width deviates greatly from the set
|
|
* @wideglyph_width value.
|
|
* @s and @src must NOT overlap: the copy is performed
|
|
* with strlcpy(), which has undefined behaviour for
|
|
* overlapping buffers (and aborts via __chk_fail_overlap
|
|
* with fortified libc on macOS).
|
|
**/
|
|
size_t word_wrap_wideglyph(char *s, size_t len,
|
|
const char *src, size_t src_len, int line_width,
|
|
int wideglyph_width, unsigned max_lines)
|
|
{
|
|
char *lastspace = NULL;
|
|
char *lastwideglyph = NULL;
|
|
const char *src_end = src + src_len;
|
|
char *s_start = s;
|
|
unsigned lines = 1;
|
|
unsigned space_counter = 0;
|
|
bool space_on_line = false;
|
|
/* 'line_width' means max numbers of characters per line,
|
|
* but this metric is only meaningful when dealing with
|
|
* 'regular' glyphs that have an on-screen pixel width
|
|
* similar to that of regular Latin characters.
|
|
* When handing so-called 'wide' Unicode glyphs, it is
|
|
* necessary to consider the actual on-screen pixel width
|
|
* of each character.
|
|
* In order to do this, we create a distinction between
|
|
* regular Latin 'non-wide' glyphs and 'wide' glyphs, and
|
|
* normalise all values relative to the on-screen pixel
|
|
* width of regular Latin characters:
|
|
* - Regular 'non-wide' glyphs have a normalised width of 100
|
|
* - 'line_width' is therefore normalised to 100 * (width_in_characters)
|
|
* - 'wide' glyphs have a normalised width of
|
|
* 100 * (wide_character_pixel_width / latin_character_pixel_width)
|
|
* - When a character is detected, the position in the current
|
|
* line is incremented by the regular normalised width of 100
|
|
* - If that character is then determined to be a 'wide'
|
|
* glyph, the position in the current line is further incremented
|
|
* by the difference between the normalised 'wide' and 'non-wide'
|
|
* width values */
|
|
unsigned counter_normalized = 0;
|
|
int line_width_normalized = line_width * 100;
|
|
int additional_counter_normalized = wideglyph_width - 100;
|
|
|
|
/* Early return if src string length is less
|
|
* than line width.
|
|
*
|
|
* NOTE on the strlcpy clamp: strlcpy returns strlen(src),
|
|
* which exceeds bytes-actually-written if the destination
|
|
* was too small (truncation case). Callers (xmb, ozone,
|
|
* materialui messagebox helpers) use the returned value as
|
|
* the length argument to memchr() over the destination
|
|
* buffer; an inflated return walks memchr past the buffer
|
|
* end into adjacent stack memory. Clamp the return to the
|
|
* true bytes-written count: min(strlen(src), len - 1). */
|
|
if (src_end - src < line_width)
|
|
{
|
|
size_t copied = strlcpy(s, src, len);
|
|
if (copied >= len)
|
|
copied = (len > 0) ? len - 1 : 0;
|
|
return copied;
|
|
}
|
|
|
|
while (*src != '\0')
|
|
{
|
|
size_t remaining;
|
|
unsigned char_len = (unsigned char)*src < 0x80
|
|
? 1 : (unsigned)(utf8skip(src, 1) - src);
|
|
counter_normalized += 100;
|
|
|
|
/* Prevent buffer overflow. `remaining` is computed from
|
|
* the original `len` and the current write offset rather
|
|
* than tracking it via `len -= char_len` -- the rewinds
|
|
* at lastspace/lastwideglyph below move `s` backwards
|
|
* without a matching `len += ...`, which would otherwise
|
|
* desync the two and break the buffer-space accounting
|
|
* for the early-return strlcpys. */
|
|
remaining = len - (size_t)(s - s_start);
|
|
if (char_len >= remaining)
|
|
break;
|
|
|
|
if (*src == ' ')
|
|
{
|
|
lastspace = s;
|
|
space_counter = counter_normalized;
|
|
space_on_line = true;
|
|
}
|
|
else if (*src == '\n')
|
|
{
|
|
/* If newlines embedded in the input,
|
|
* reset the index */
|
|
lines++;
|
|
counter_normalized = 0;
|
|
space_on_line = false;
|
|
|
|
/* Early return if remaining src string
|
|
* length is less than line width */
|
|
if (src_end - src <= line_width)
|
|
{
|
|
size_t copied = strlcpy(s, src, remaining);
|
|
if (copied >= remaining)
|
|
copied = (remaining > 0) ? remaining - 1 : 0;
|
|
return (size_t)(s - s_start) + copied;
|
|
}
|
|
}
|
|
else if (char_len >= 3)
|
|
{
|
|
/* Remember the location of the first byte
|
|
* whose length as UTF-8 >= 3*/
|
|
lastwideglyph = s;
|
|
counter_normalized += additional_counter_normalized;
|
|
}
|
|
|
|
/* A break before this glyph need not copy and decode it twice. */
|
|
if (lastwideglyph == s && char_len >= 3
|
|
&& counter_normalized >= (unsigned)line_width_normalized
|
|
&& (max_lines == 0 || lines < max_lines)
|
|
&& (unsigned)wideglyph_width < (unsigned)line_width_normalized
|
|
&& char_len + 1 < remaining)
|
|
{
|
|
*s++ = '\n';
|
|
lines++;
|
|
if (src_end - src <= line_width)
|
|
{
|
|
size_t copied;
|
|
remaining--;
|
|
copied = strlcpy(s, src, remaining);
|
|
if (copied >= remaining)
|
|
copied = remaining - 1;
|
|
return (size_t)(s - s_start) + copied;
|
|
}
|
|
lastwideglyph = s;
|
|
counter_normalized = (unsigned)wideglyph_width;
|
|
space_on_line = false;
|
|
memcpy(s, src, char_len);
|
|
s += char_len;
|
|
src += char_len;
|
|
continue;
|
|
}
|
|
|
|
while (char_len--)
|
|
*s++ = *src++;
|
|
|
|
if (counter_normalized >= (unsigned)line_width_normalized)
|
|
{
|
|
unsigned scanned = counter_normalized;
|
|
bool carry_space = space_on_line;
|
|
counter_normalized = 0;
|
|
space_on_line = false;
|
|
|
|
if (max_lines != 0 && lines >= max_lines)
|
|
continue;
|
|
else if (lastwideglyph
|
|
&& (!lastspace || lastwideglyph > lastspace))
|
|
{
|
|
/* Insert newline character */
|
|
*lastwideglyph = '\n';
|
|
lines++;
|
|
src -= s - lastwideglyph;
|
|
s = lastwideglyph + 1;
|
|
lastwideglyph = NULL;
|
|
|
|
/* Early return if remaining src string
|
|
* length is less than line width */
|
|
if (src_end - src <= line_width)
|
|
{
|
|
size_t copied;
|
|
remaining = len - (size_t)(s - s_start);
|
|
copied = strlcpy(s, src, remaining);
|
|
if (copied >= remaining)
|
|
copied = (remaining > 0) ? remaining - 1 : 0;
|
|
return (size_t)(s - s_start) + copied;
|
|
}
|
|
}
|
|
else if (lastspace)
|
|
{
|
|
/* Replace nearest (previous) whitespace
|
|
* with newline character */
|
|
*lastspace = '\n';
|
|
lines++;
|
|
/* The tail after this space has already been measured. */
|
|
if (carry_space
|
|
&& src_end - (src - (s - lastspace - 1)) >= line_width)
|
|
{
|
|
counter_normalized = scanned - space_counter;
|
|
lastspace = NULL;
|
|
continue;
|
|
}
|
|
src -= s - lastspace - 1;
|
|
s = lastspace + 1;
|
|
lastspace = NULL;
|
|
|
|
/* Early return if remaining src string
|
|
* length is less than line width */
|
|
if (src_end - src < line_width)
|
|
{
|
|
size_t copied;
|
|
remaining = len - (size_t)(s - s_start);
|
|
copied = strlcpy(s, src, remaining);
|
|
if (copied >= remaining)
|
|
copied = (remaining > 0) ? remaining - 1 : 0;
|
|
return (size_t)(s - s_start) + copied;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*s = '\0';
|
|
return (size_t)(s - s_start);
|
|
}
|
|
|
|
/**
|
|
* string_tokenize:
|
|
*
|
|
* Splits string into tokens separated by @delim
|
|
* > Returned token string must be free()'d
|
|
* > Returns NULL if token is not found
|
|
* > After each call, @str is set to the position after the
|
|
* last found token
|
|
* > Tokens *include* empty strings
|
|
* Usage example:
|
|
* char *str = "1,2,3,4,5,6,7,,,10,";
|
|
* char **str_ptr = &str;
|
|
* char *token = NULL;
|
|
* while ((token = string_tokenize(str_ptr, ",")))
|
|
* {
|
|
* printf("%s\n", token);
|
|
* free(token);
|
|
* token = NULL;
|
|
* }
|
|
**/
|
|
char* string_tokenize(char **str, const char *delim)
|
|
{
|
|
/* Taken from https://codereview.stackexchange.com/questions/216956/strtok-function-thread-safe-supports-empty-tokens-doesnt-change-string# */
|
|
char *str_ptr = NULL;
|
|
char *delim_ptr = NULL;
|
|
char *token = NULL;
|
|
size_t _len = 0;
|
|
size_t delim_len = 0;
|
|
|
|
/* Sanity checks */
|
|
if (!str || !delim || !*delim)
|
|
return NULL;
|
|
|
|
/* Note: we don't check if string is empty here,
|
|
* empty strings are valid */
|
|
if (!(str_ptr = *str))
|
|
return NULL;
|
|
|
|
delim_len = strlen(delim);
|
|
|
|
/* Search for delimiter */
|
|
if ((delim_ptr = strstr(str_ptr, delim)))
|
|
_len = delim_ptr - str_ptr;
|
|
else
|
|
_len = strlen(str_ptr);
|
|
|
|
/* Allocate token string */
|
|
if (!(token = (char *)malloc((_len + 1) * sizeof(char))))
|
|
return NULL;
|
|
|
|
/* Copy token. strlcpy already NUL-terminates within the
|
|
* `_len + 1` byte limit -- _len is bounded above by
|
|
* strlen(str_ptr) (computed at lines 489-492), so the
|
|
* terminator lands exactly at token[_len] without needing
|
|
* a separate write here. The redundant token[_len] = '\0'
|
|
* also tripped -Wstringop-overflow under some gcc
|
|
* configurations (the analyser couldn't constrain _len
|
|
* relative to the malloc'd size). */
|
|
strlcpy(token, str_ptr, (_len + 1) * sizeof(char));
|
|
/* Update input string pointer */
|
|
*str = delim_ptr ? delim_ptr + delim_len : NULL;
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* string_remove_all_chars:
|
|
* @s : input string (must be non-NULL, otherwise UB)
|
|
*
|
|
* Leaf function.
|
|
*
|
|
* Removes every instance of character @c from @s
|
|
*
|
|
* Returns the length of the resulting string.
|
|
**/
|
|
size_t string_remove_all_chars(char *s, char c)
|
|
{
|
|
char *dst = s;
|
|
char *src = s;
|
|
while (*src)
|
|
{
|
|
/* Only write if the character is not the one to remove */
|
|
if (*src != c)
|
|
*dst++ = *src;
|
|
src++;
|
|
}
|
|
*dst = '\0';
|
|
return (size_t)(dst - s);
|
|
}
|
|
|
|
/**
|
|
* string_replace_all_chars:
|
|
* @s : input string (must be non-NULL, otherwise UB)
|
|
* @find : character to find
|
|
* @replace : character to replace @find with
|
|
*
|
|
* Replaces every instance of character @find in @s
|
|
* with character @replace
|
|
**/
|
|
void string_replace_all_chars(char *s, char find, char replace)
|
|
{
|
|
char *str_ptr = s;
|
|
while ((str_ptr = strchr(str_ptr, find)))
|
|
*str_ptr++ = replace;
|
|
}
|
|
|
|
/**
|
|
* string_to_unsigned:
|
|
* @str : input string
|
|
*
|
|
* Converts string to unsigned integer.
|
|
*
|
|
* @return 0 if string is invalid, otherwise > 0
|
|
**/
|
|
unsigned string_to_unsigned(const char *str)
|
|
{
|
|
const char *ptr = NULL;
|
|
|
|
if (!str || !*str)
|
|
return 0;
|
|
|
|
for (ptr = str; *ptr != '\0'; ptr++)
|
|
{
|
|
if (!ISDIGIT((unsigned char)*ptr))
|
|
return 0;
|
|
}
|
|
|
|
return (unsigned)strtoul(str, NULL, 10);
|
|
}
|
|
|
|
/**
|
|
* string_hex_to_unsigned:
|
|
* @str : input string (must be non-NULL, otherwise UB)
|
|
*
|
|
* Converts hexadecimal string to unsigned integer.
|
|
* Handles optional leading '0x'.
|
|
*
|
|
* @return 0 if string is invalid, otherwise > 0
|
|
**/
|
|
unsigned string_hex_to_unsigned(const char *str)
|
|
{
|
|
const char *hex_str = str;
|
|
const char *ptr = NULL;
|
|
|
|
if (!str || !*str)
|
|
return 0;
|
|
|
|
/* Remove leading '0x', if present */
|
|
if ( str[0] == '0'
|
|
&& (str[1] == 'x' || str[1] == 'X'))
|
|
{
|
|
hex_str = str + 2;
|
|
if (!hex_str || !*hex_str)
|
|
return 0;
|
|
}
|
|
|
|
/* Check for valid characters */
|
|
for (ptr = hex_str; *ptr != '\0'; ptr++)
|
|
{
|
|
if (!isxdigit((unsigned char)*ptr))
|
|
return 0;
|
|
}
|
|
|
|
return (unsigned)strtoul(hex_str, NULL, 16);
|
|
}
|
|
|
|
static int string_hexval(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if (c >= 'a' && c <= 'f')
|
|
return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
/* Both digits are read before anything is written, so s == src
|
|
* (in place) is safe. */
|
|
int string_percent_decode(char *s, size_t len, const char *src)
|
|
{
|
|
size_t _len = 0;
|
|
if (!s || !len || !src)
|
|
return -1;
|
|
while (*src)
|
|
{
|
|
char c = *src;
|
|
if (c == '%')
|
|
{
|
|
int hi = string_hexval(src[1]);
|
|
int lo = (hi >= 0) ? string_hexval(src[2]) : -1;
|
|
if (lo >= 0)
|
|
{
|
|
c = (char)((hi << 4) | lo);
|
|
src += 2;
|
|
}
|
|
}
|
|
if (_len + 1 >= len)
|
|
{
|
|
s[_len] = '\0';
|
|
return -1;
|
|
}
|
|
s[_len++] = c;
|
|
src++;
|
|
}
|
|
s[_len] = '\0';
|
|
return (int)_len;
|
|
}
|
|
|
|
/**
|
|
* string_count_occurrences_single_character:
|
|
*
|
|
* Leaf function.
|
|
*
|
|
* Get the total number of occurrences of character @c in @str.
|
|
*
|
|
* @return Total number of occurrences of character @c
|
|
*/
|
|
int string_count_occurrences_single_character(const char *str, char c)
|
|
{
|
|
int count = 0;
|
|
|
|
for (; *str; str++)
|
|
if (*str == c)
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* string_replace_whitespace_with_single_character:
|
|
*
|
|
* Leaf function.
|
|
*
|
|
* Replaces all spaces with given character @c.
|
|
**/
|
|
void string_replace_whitespace_with_single_character(char *s, char c)
|
|
{
|
|
for (; *s; s++)
|
|
if (ISSPACE((unsigned char)*s))
|
|
*s = c;
|
|
}
|
|
|
|
/**
|
|
* string_replace_multi_space_with_single_space:
|
|
*
|
|
* Leaf function.
|
|
*
|
|
* Replaces multiple spaces with a single space in a string.
|
|
**/
|
|
void string_replace_multi_space_with_single_space(char *s)
|
|
{
|
|
char *str_trimmed = s;
|
|
bool prev_is_space = false;
|
|
bool curr_is_space = false;
|
|
|
|
for (; *s; s++)
|
|
{
|
|
curr_is_space = ISSPACE((unsigned char)*s);
|
|
if (prev_is_space && curr_is_space)
|
|
continue;
|
|
*str_trimmed++ = *s;
|
|
prev_is_space = curr_is_space;
|
|
}
|
|
*str_trimmed = '\0';
|
|
}
|
|
|
|
/**
|
|
* string_remove_all_whitespace:
|
|
*
|
|
* Leaf function.
|
|
*
|
|
* Remove all spaces from the given string.
|
|
* Returns the length of the resulting string.
|
|
**/
|
|
size_t string_remove_all_whitespace(char *s, const char *str)
|
|
{
|
|
char *start = (char*)s;
|
|
for (; *str; str++)
|
|
if (!ISSPACE((unsigned char)*str))
|
|
*s++ = *str;
|
|
*s = '\0';
|
|
return s - start;
|
|
}
|
|
|
|
/**
|
|
* strlcpy_append:
|
|
*
|
|
* See header (libretro-common/include/string/stdstring.h) for the
|
|
* full contract. Bound-checked append; advances *pos by
|
|
* strlen(@src) on success, returns -1 on truncation.
|
|
*
|
|
* Truncation is signalled when *pos >= len already, or when
|
|
* strlcpy returns a value >= the remaining capacity. In either
|
|
* case the destination is left NUL-terminated (strlcpy guarantees
|
|
* this when its size argument is non-zero) and *pos is clamped to
|
|
* len - 1 so subsequent calls in a chain become no-ops that also
|
|
* return -1.
|
|
**/
|
|
int strlcpy_append(char *s, size_t len, size_t *pos, const char *src)
|
|
{
|
|
size_t remaining;
|
|
size_t n;
|
|
|
|
if (!s || !pos || !src || len == 0)
|
|
return -1;
|
|
|
|
if (*pos >= len)
|
|
{
|
|
/* Already saturated; clamp and report truncation. */
|
|
*pos = len - 1;
|
|
return -1;
|
|
}
|
|
|
|
remaining = len - *pos;
|
|
n = strlcpy(s + *pos, src, remaining);
|
|
|
|
if (n >= remaining)
|
|
{
|
|
/* strlcpy truncated. s + len - 1 is NUL-terminated by
|
|
* strlcpy's contract. Clamp *pos so subsequent appends
|
|
* short-circuit. */
|
|
*pos = len - 1;
|
|
return -1;
|
|
}
|
|
|
|
*pos += n;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the last occurrence of the given character in a string.
|
|
*/
|
|
int string_index_last_occurance(const char *str, char c)
|
|
{
|
|
const char *pos = strrchr(str, c);
|
|
if (pos)
|
|
return (int)(pos - str);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* string_find_index_substring_string:
|
|
* @str : input string (must be non-NULL, otherwise UB)
|
|
* @substr : substring to find in @str
|
|
*
|
|
* Find the position of substring @substr in string @str.
|
|
**/
|
|
int string_find_index_substring_string(const char *str, const char *substr)
|
|
{
|
|
const char *pos = strstr(str, substr);
|
|
if (pos)
|
|
return (int)(pos - str);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* string_copy_only_ascii:
|
|
*
|
|
* Leaf function.
|
|
*
|
|
* Strips non-ASCII characters from a string.
|
|
**/
|
|
void string_copy_only_ascii(char *s, const char *str)
|
|
{
|
|
for (; *str; str++)
|
|
if (*str > 0x1F && *str < 0x7F)
|
|
*s++ = *str;
|
|
*s = '\0';
|
|
}
|
|
|
|
/**
|
|
* string_ext_list_find:
|
|
*
|
|
* Checks whether a single extension token already exists
|
|
* in a '|'-delimited string. Exact token matching only.
|
|
**/
|
|
bool string_ext_list_find(const char *delim_str, size_t delim_len,
|
|
const char *ext, size_t ext_len)
|
|
{
|
|
const char *p = delim_str;
|
|
const char *end = delim_str + delim_len;
|
|
|
|
while (p < end)
|
|
{
|
|
const char *tok_end = (const char*)memchr(p, '|', end - p);
|
|
size_t tok_len;
|
|
|
|
if (!tok_end)
|
|
tok_end = end;
|
|
|
|
tok_len = tok_end - p;
|
|
|
|
if (tok_len == ext_len && memcmp(p, ext, ext_len) == 0)
|
|
return true;
|
|
|
|
p = tok_end + 1;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* string_ext_list_append_dedup:
|
|
*
|
|
* Appends a single extension to a '|'-delimited destination buffer,
|
|
* but only if that extension is not already present.
|
|
**/
|
|
void string_ext_list_append_dedup(char *dst, size_t *dst_len,
|
|
size_t dst_size, const char *ext, size_t ext_len)
|
|
{
|
|
if (ext_len == 0)
|
|
return;
|
|
if (string_ext_list_find(dst, *dst_len, ext, ext_len))
|
|
return;
|
|
if (*dst_len + 1 + ext_len + 1 > dst_size)
|
|
return;
|
|
|
|
if (*dst_len > 0)
|
|
dst[(*dst_len)++] = '|';
|
|
|
|
memcpy(dst + *dst_len, ext, ext_len);
|
|
*dst_len += ext_len;
|
|
dst[*dst_len] = '\0';
|
|
}
|
|
|
|
/**
|
|
* string_ext_list_merge_dedup:
|
|
*
|
|
* Splits a '|'-delimited source string and appends each unique
|
|
* extension to the destination buffer via string_ext_list_append_dedup.
|
|
**/
|
|
void string_ext_list_merge_dedup(char *dst, size_t *dst_len,
|
|
size_t dst_size, const char *src)
|
|
{
|
|
const char *p;
|
|
const char *end;
|
|
|
|
if (!src || !*src)
|
|
return;
|
|
|
|
end = src + strlen(src);
|
|
p = src;
|
|
|
|
while (p < end)
|
|
{
|
|
const char *tok_end = (const char*)memchr(p, '|', end - p);
|
|
size_t tok_len;
|
|
|
|
if (!tok_end)
|
|
tok_end = end;
|
|
|
|
tok_len = tok_end - p;
|
|
|
|
if (tok_len > 0)
|
|
string_ext_list_append_dedup(dst, dst_len, dst_size, p, tok_len);
|
|
|
|
p = tok_end + 1;
|
|
}
|
|
}
|