string-util: add strnspn()

Like strspn(), but reads at most 'n' bytes from the input. strspn()
is bounded only by a non-matching byte or a NUL, so it over-reads a
buffer that is all matching bytes and not NUL terminated within 'n'.
This commit is contained in:
Syed Mohammed Nayyar
2026-06-29 11:32:14 +05:30
parent e61a0705db
commit 0af6caff42
3 changed files with 38 additions and 0 deletions

View File

@@ -1389,6 +1389,23 @@ size_t strspn_from_end(const char *str, const char *accept) {
return n;
}
size_t strnspn(const char *str, const char *accept, size_t n) {
size_t i;
/* Like strspn(), but reads at most 'n' bytes from 'str'. Returns the length of the initial
* run of 'str' (capped at 'n') that consists solely of bytes found in 'accept'. Stops at a
* NUL byte too. Unlike strspn() this is safe on a buffer that is not NUL terminated within
* 'n' bytes. */
assert(str || n == 0);
assert(accept);
for (i = 0; i < n && str[i] != '\0' && strchr(accept, str[i]); i++)
;
return i;
}
char* strdupspn(const char *a, const char *accept) {
if (isempty(a) || isempty(accept))
return strdup("");

View File

@@ -286,6 +286,7 @@ typedef enum MakeCStringMode {
int make_cstring(const void *s, size_t n, MakeCStringMode mode, char **ret);
size_t strspn_from_end(const char *str, const char *accept) _pure_;
size_t strnspn(const char *str, const char *accept, size_t n) _pure_ _nonnull_if_nonzero_(1, 3);
char* strdupspn(const char *a, const char *accept);
char* strdupcspn(const char *a, const char *reject);

View File

@@ -1286,6 +1286,26 @@ TEST(strspn_from_end) {
assert_se(strspn_from_end("aaa12aa34", DIGITS) == 2);
}
TEST(strnspn) {
assert_se(strnspn(NULL, WHITESPACE, 0) == 0);
assert_se(strnspn("", WHITESPACE, 0) == 0);
assert_se(strnspn("hoge", WHITESPACE, 0) == 0);
assert_se(strnspn("hoge", WHITESPACE, 4) == 0);
assert_se(strnspn(" hoge", WHITESPACE, 7) == 3);
assert_se(strnspn(" hoge", WHITESPACE, 2) == 2);
assert_se(strnspn(" ", WHITESPACE, 4) == 4);
/* 'str' is all whitespace and not NUL terminated within 'n': at most 'n' bytes may be read,
* otherwise this reads off the end of the buffer (caught by the sanitizers). */
for (size_t n = 1; n <= 64; n++) {
_cleanup_free_ char *str = NULL;
assert_se(str = new(char, n));
memset(str, ' ', n);
assert_se(strnspn(str, WHITESPACE, n) == n);
}
}
TEST(streq_skip_trailing_chars) {
/* NULL is WHITESPACE by default */
assert_se(streq_skip_trailing_chars("foo bar", "foo bar", NULL));