libsheepy

C lib for handling text files, strings and json like data structure with an object oriented system
git clone https://spartatek.se/git/libsheepy.git
Log | Files | Refs | README | LICENSE

commit a54890e3f82bd8918da03e4af42fd88b2250db43
parent d87a7c2c90bfc67eae0cdd59614dbdca99b04200
Author: Remy Noulin <loader2x@gmail.com>
Date:   Sat, 10 May 2025 18:25:14 +0200

fix segfault in expandHome when path is empty, add setValB to replace a btt with another one, add isEmptyB to check if a btt is empty, add isValidB to check if a btt has correct values, add eqCharB to compare a btt to a char, add joinCharB to join a vbtt list, add bCompactB to compact a vbtt list, add bNormalizePathB to normalize a btt holding a path

release/libsheepy.c       |  24 ++++++
release/libsheepy.h       |   2 +-
release/libsheepyBt.c     | 213 +++++++++++++++++++++++++++++++++++++++++++++-
release/libsheepyBt.h     |  56 ++++++++++--
release/libsheepyObject.h |   5 ++
src/libsheepy.c           |  24 ++++++
src/libsheepy.h           |   2 +-
src/libsheepyBt.c         | 213 +++++++++++++++++++++++++++++++++++++++++++++-
src/libsheepyBt.h         |  56 ++++++++++--
src/libsheepyObject.h     |   5 ++
10 files changed, 586 insertions(+), 14 deletions(-)

Diffstat:
Mrelease/libsheepy.c | 24++++++++++++++++++++++++
Mrelease/libsheepy.h | 2+-
Mrelease/libsheepyBt.c | 213++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mrelease/libsheepyBt.h | 56+++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mrelease/libsheepyObject.h | 5+++++
Msrc/libsheepy.c | 24++++++++++++++++++++++++
Msrc/libsheepy.h | 2+-
Msrc/libsheepyBt.c | 213++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/libsheepyBt.h | 56+++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/libsheepyObject.h | 5+++++
10 files changed, 586 insertions(+), 14 deletions(-)

diff --git a/release/libsheepy.c b/release/libsheepy.c @@ -2465,6 +2465,12 @@ char *expandHome(const char* path) { // duplicate path to be able to realloc (impossible when path is static) p = strdup(path); + if (path[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(p); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(p, &exp_result, 0);; @@ -2656,6 +2662,12 @@ char *iExpandHome(char **path) { return(NULL); } + if ((*path)[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(*path); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(*path, &exp_result, 0);; @@ -2835,6 +2847,12 @@ char *bExpandHome(char *path) { return(NULL); } + if (path[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(path); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(path, &exp_result, 0);; @@ -3015,6 +3033,12 @@ char *bLExpandHome(char *path, size_t pathSize) { return(NULL); } + if (path[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(path); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(path, &exp_result, 0);; diff --git a/release/libsheepy.h b/release/libsheepy.h @@ -98,7 +98,7 @@ // version accoring to the version package: Release.Major.minor.patch // https://noulin.net/version/file/README.md.html -#define LIBSHEEPY_VERSION "2.2.18.1" +#define LIBSHEEPY_VERSION "2.2.19" #ifndef SH_PREFIX #define SH_PREFIX(NAME) NAME diff --git a/release/libsheepyBt.c b/release/libsheepyBt.c @@ -138,7 +138,7 @@ btt *allocPB(const btt *b) { * return a btt object with a heap allocated copy of b.b */ btt copyB(const btt b) { - btt r = b; + btt r = b; r.b = malloc(b.len); r.alloc = b.len ? b.len : 4 /*allocate 4 bytes when len is 0*/; memcpy(r.b, b.b, b.len); @@ -164,6 +164,13 @@ btt *dupPB(btt *b) { ret dupB(*b); } +btt *setValB(btt *b, btt *a) { + if (!a or !b) ret null; + freenB(b); + *b = *a; + ret b; +} + /** * free heap allocated .b * nothing if not heap allocated @@ -239,6 +246,32 @@ char *toCharB(btt *b) { ret r; } +bool isEmptyB(const btt b) { + if (not b.b or not b.len) ret yes; + ret no; +} + +bool isEmptyPB(btt *b) { + if (not b) ret yes; + ret isEmptyB(*b); +} + +bool isValidB(const btt b) { + if (not b.b or (b.alloc and b.len > b.alloc)) ret no; + ret yes; +} + +bool isValidPB(btt *b) { + if (not b) ret no; + ret isValidB(*b); +} + +bool eqCharB(btt a, char *b) { + if (not b or not a.b) ret no; + if (a.len != strlen(b)) ret no; + ret memcmp(a.b, b, a.len) == 0; +} + /** * return a btt object with s appended to b * b doesn't need to have a heap allocated buffer @@ -1124,6 +1157,39 @@ btt slicePB(const btt *b, int64_t start, int64_t end) { ret sliceB(*b, start, end); } +/** + * join list, the elements are seperated with delim in the resulting string + * + * \param + * list + * \param + * delim: string seperator + * \return + * joined string (you must free the btt) + * empty when list or delim are NULL + */ +btt joinCharB(vbtt list, const char* delim) { + btt r = {0}; + + // sanity checks + if (!delim) { + return r; + } + + vectorForEach(&list, e) { + if (!r.b) { + r = copyB(*e); + } + else { + // TODO check return value + bPushB(&r, delim); + bPushBB(&r, *e); + } + } + return(r); +} + + btt copyRngB(const btt b, int64_t start, int64_t end) { btt s = sliceB(b, start, end); if (not s.b) ret s; // error return {0} @@ -1230,6 +1296,151 @@ btt trimPB(const btt *b) { ret trimB(*b); } +/** + * remove empty strings from list + * + * \param + * list + * \return + * list without empty strings + * empty list when list is empty + * unchanged list when list is NULL + * NULL error + */ +vbtt *bCompactB(vbtt *list) { + + // sanity checks + if (!list) { + return(NULL); + } + + u16 rindex = 0; + // keep non empty elements + vectorForEach(list, e) { + trimBG(e); + if (not isEmptyPB(e)) { + list->array[rindex++] = *e; + } + else { + freenB(e); + } + } + list->count = rindex; + return(list); +} + +/** + * buffer size normalize path + * + * remove unecessary /, .. and . + * leading / is kept + * leading .. is kept + * leading . is removed + * + * '/../' becomes '/' + * + * \param + * path + * \return + * path modified path + * NULL when path is NULL + */ +btt *bNormalizePathB(btt *path) { + + // sanity checks + if (!path) { + return(NULL); + } + + if (!path->len) { + return(path); + } + + if (isEmptyPB(path)) { + return(path); + } + + // list path elements + vbtt pathL = splitBG(path, "/"); + + // remove empty elements + bCompactB(&pathL); + + if (pathL.count == 0) { + vectorFree(&pathL); + // keep leading / + path->b[0] = '/'; + path->len = 1; + return(path); + } + + // new path elements + vbtt list; + vectorInitCount(&list, pathL.count); + + // detect leading double dots + bool onlyLeadingDoubleDots = true; + + // add elements to list + vectorForEach(&pathL, level) { + if (eqCharB(*level, "..")) { + if (onlyLeadingDoubleDots) { + // keep leading .. + vectorAppend(&list, charB(strdup(".."))); + } + else { + // remove .. in path + if (list.count) { + btt s = vectorPop(&list); + freeB(s); + } + } + } + else if (!eqCharB(*level, ".")) { + // remove . and add elements + btt c = copyB(*level); + vectorAppend(&list, c); + // an element is pushed, so this is the end of leading double dots + onlyLeadingDoubleDots = false; + } + } + + if (list.count == 1 && eqCharB(vectorAt(&list, 0), "..") && path->b[0] == '/') { + // handle ../ .. /.. /../ + vectorAt(&list, 0).len = 0; + } + + // handle /.: add empty string + if (eqCharB(*path, "/.")) { + btt c = newB(4); + vectorAppend(&list, c); + } + + // keep leading / + if (path->b[0] == '/') { + if (list.count == 0) { + // .. cancelled path: /a/b/../.. + vectorFree(&pathL); + vectorFree(&list); + path->len = 1; + return(path); + } + //why? pErrorNULL(listPrependS(&list, "")); + } + + // create new path + btt r = joinCharB(list, "/"); + vectorFree(&pathL); + vectorFree(&list); + if (!r.b) { + path->len = 0; + return(path); + } + // TODO check null + setValB(path, &r); + return(path); +} + void printB(const btt b) { write(STDOUT_FILENO, b.b, b.len); } diff --git a/release/libsheepyBt.h b/release/libsheepyBt.h @@ -65,7 +65,7 @@ * * ``` * btt mystring = ccharB("Hello World!"); - * btt mystring2 = charB(strdup("Hello World!"), true); + * btt mystring2 = charB(strdup("Hello World!")); * btt mybuf = voidB("Binary", sizeof("Binary"), false); * createCharB(mystring3, "bytes", false); * // from another string: @@ -137,13 +137,13 @@ * * ``` * btt ss = ccharB("Hello World!"); - * vbtt lsspl = splitB(ss, " "); + * vbtt ll = splitB(ss, " "); * puts(""); * vectorForEach(&ll, e) { * printB(*e); * puts(""); * } - * vectorFree(&lsspl); + * vectorFree(&ll); * ``` * * @@ -186,7 +186,7 @@ * Empty btt: * btt s = {0}; * Assign C string: - * s = charB("a string", no); + * s = voidB("a string", no); * s.b = "asdasd"; * b.len = strlen(s.b); * @@ -378,6 +378,9 @@ btt *dupPB(btt *b); btt*: dupPB\ )(b) +// assign a to b, b is freed if it was allocated +btt *setValB(btt *b, btt *a); + // TODO: // ..lenB // ..freeB @@ -396,7 +399,7 @@ btt *dupPB(btt *b); // ..splitB // splitLenB // listFreeB -// join +// ..join // ..toCharB convert to char* // getB setB setBB set .b getBB get .b // @@ -451,6 +454,16 @@ void cleanUpFinishB(btt **val); // convert btt to char* allocated on heap char *toCharB(btt *b); +bool isEmptyB(const btt b); + +bool isEmptyPB(btt *b); + +bool isValidB(const btt b); + +bool isValidPB(btt *b); + +bool eqCharB(btt a, char *b); + // return a btt object with s appended to b // b doesn't need to have a heap allocated buffer #define appendB pushB @@ -837,6 +850,8 @@ btt slicePB(const btt *b, int64_t start, int64_t end); const btt*: slicePB\ )(b, start, end); +btt joinCharB(vbtt list, const char* delim); + // copyRngB // return a btt object with a heap allocated copy of b.b from start to end btt copyRngB(const btt b, int64_t start, int64_t end); @@ -864,6 +879,37 @@ btt trimPB(const btt *b); const btt*: trimPB\ )(b) +/** + * remove empty strings from list + * + * \param + * list + * \return + * list without empty strings + * empty list when list is empty + * unchanged list when list is NULL + * NULL error + */ +vbtt *bCompactB(vbtt *list); + +/** + * buffer size normalize path + * + * remove unecessary /, .. and . + * leading / is kept + * leading .. is kept + * leading . is removed + * + * '/../' becomes '/' + * + * \param + * path + * \return + * path modified path + * NULL when path is NULL + */ +btt *bNormalizePathB(btt *path); + void printB(const btt b); void printDebugB(const btt b); diff --git a/release/libsheepyObject.h b/release/libsheepyObject.h @@ -2895,6 +2895,11 @@ void finishManyOF(void *paramType, ...); char ***: iListShiftNSmashS \ )(self, obj) +// Delete an element in an array and move the elements after +// to fill the gap (delElemG deletes the element and creates a gap) +// the indices of the elements after are changed (-1) +#define delElG(self, idx) delG(self, idx, (idx)+1) + #define delO(self, start, end) (self)->f->del(self, start, end) #define delG(self, start, end) _Generic((self), \ smallDictt*: _Generic(start, \ diff --git a/src/libsheepy.c b/src/libsheepy.c @@ -2521,6 +2521,12 @@ char *expandHome(const char* path) { // duplicate path to be able to realloc (impossible when path is static) p = strdup(path); + if (path[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(p); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(p, &exp_result, 0);; @@ -2712,6 +2718,12 @@ char *iExpandHome(char **path) { return(NULL); } + if ((*path)[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(*path); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(*path, &exp_result, 0);; @@ -2891,6 +2903,12 @@ char *bExpandHome(char *path) { return(NULL); } + if (path[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(path); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(path, &exp_result, 0);; @@ -3071,6 +3089,12 @@ char *bLExpandHome(char *path, size_t pathSize) { return(NULL); } + if (path[0] == 0) { + // path is empty + // when path is empty, exp_result.we_wordv[0] is null and strlen segfaults + return(path); + } + #if (!__TERMUX__) // expand ~/ int status = wordexp(path, &exp_result, 0);; diff --git a/src/libsheepy.h b/src/libsheepy.h @@ -98,7 +98,7 @@ // version accoring to the version package: Release.Major.minor.patch // https://noulin.net/version/file/README.md.html -#define LIBSHEEPY_VERSION "2.2.18.1" +#define LIBSHEEPY_VERSION "2.2.19" #ifndef SH_PREFIX #define SH_PREFIX(NAME) NAME diff --git a/src/libsheepyBt.c b/src/libsheepyBt.c @@ -138,7 +138,7 @@ btt *allocPB(const btt *b) { * return a btt object with a heap allocated copy of b.b */ btt copyB(const btt b) { - btt r = b; + btt r = b; r.b = malloc(b.len); r.alloc = b.len ? b.len : 4 /*allocate 4 bytes when len is 0*/; memcpy(r.b, b.b, b.len); @@ -164,6 +164,13 @@ btt *dupPB(btt *b) { ret dupB(*b); } +btt *setValB(btt *b, btt *a) { + if (!a or !b) ret null; + freenB(b); + *b = *a; + ret b; +} + /** * free heap allocated .b * nothing if not heap allocated @@ -239,6 +246,32 @@ char *toCharB(btt *b) { ret r; } +bool isEmptyB(const btt b) { + if (not b.b or not b.len) ret yes; + ret no; +} + +bool isEmptyPB(btt *b) { + if (not b) ret yes; + ret isEmptyB(*b); +} + +bool isValidB(const btt b) { + if (not b.b or (b.alloc and b.len > b.alloc)) ret no; + ret yes; +} + +bool isValidPB(btt *b) { + if (not b) ret no; + ret isValidB(*b); +} + +bool eqCharB(btt a, char *b) { + if (not b or not a.b) ret no; + if (a.len != strlen(b)) ret no; + ret memcmp(a.b, b, a.len) == 0; +} + /** * return a btt object with s appended to b * b doesn't need to have a heap allocated buffer @@ -1124,6 +1157,39 @@ btt slicePB(const btt *b, int64_t start, int64_t end) { ret sliceB(*b, start, end); } +/** + * join list, the elements are seperated with delim in the resulting string + * + * \param + * list + * \param + * delim: string seperator + * \return + * joined string (you must free the btt) + * empty when list or delim are NULL + */ +btt joinCharB(vbtt list, const char* delim) { + btt r = {0}; + + // sanity checks + if (!delim) { + return r; + } + + vectorForEach(&list, e) { + if (!r.b) { + r = copyB(*e); + } + else { + // TODO check return value + bPushB(&r, delim); + bPushBB(&r, *e); + } + } + return(r); +} + + btt copyRngB(const btt b, int64_t start, int64_t end) { btt s = sliceB(b, start, end); if (not s.b) ret s; // error return {0} @@ -1230,6 +1296,151 @@ btt trimPB(const btt *b) { ret trimB(*b); } +/** + * remove empty strings from list + * + * \param + * list + * \return + * list without empty strings + * empty list when list is empty + * unchanged list when list is NULL + * NULL error + */ +vbtt *bCompactB(vbtt *list) { + + // sanity checks + if (!list) { + return(NULL); + } + + u16 rindex = 0; + // keep non empty elements + vectorForEach(list, e) { + trimBG(e); + if (not isEmptyPB(e)) { + list->array[rindex++] = *e; + } + else { + freenB(e); + } + } + list->count = rindex; + return(list); +} + +/** + * buffer size normalize path + * + * remove unecessary /, .. and . + * leading / is kept + * leading .. is kept + * leading . is removed + * + * '/../' becomes '/' + * + * \param + * path + * \return + * path modified path + * NULL when path is NULL + */ +btt *bNormalizePathB(btt *path) { + + // sanity checks + if (!path) { + return(NULL); + } + + if (!path->len) { + return(path); + } + + if (isEmptyPB(path)) { + return(path); + } + + // list path elements + vbtt pathL = splitBG(path, "/"); + + // remove empty elements + bCompactB(&pathL); + + if (pathL.count == 0) { + vectorFree(&pathL); + // keep leading / + path->b[0] = '/'; + path->len = 1; + return(path); + } + + // new path elements + vbtt list; + vectorInitCount(&list, pathL.count); + + // detect leading double dots + bool onlyLeadingDoubleDots = true; + + // add elements to list + vectorForEach(&pathL, level) { + if (eqCharB(*level, "..")) { + if (onlyLeadingDoubleDots) { + // keep leading .. + vectorAppend(&list, charB(strdup(".."))); + } + else { + // remove .. in path + if (list.count) { + btt s = vectorPop(&list); + freeB(s); + } + } + } + else if (!eqCharB(*level, ".")) { + // remove . and add elements + btt c = copyB(*level); + vectorAppend(&list, c); + // an element is pushed, so this is the end of leading double dots + onlyLeadingDoubleDots = false; + } + } + + if (list.count == 1 && eqCharB(vectorAt(&list, 0), "..") && path->b[0] == '/') { + // handle ../ .. /.. /../ + vectorAt(&list, 0).len = 0; + } + + // handle /.: add empty string + if (eqCharB(*path, "/.")) { + btt c = newB(4); + vectorAppend(&list, c); + } + + // keep leading / + if (path->b[0] == '/') { + if (list.count == 0) { + // .. cancelled path: /a/b/../.. + vectorFree(&pathL); + vectorFree(&list); + path->len = 1; + return(path); + } + //why? pErrorNULL(listPrependS(&list, "")); + } + + // create new path + btt r = joinCharB(list, "/"); + vectorFree(&pathL); + vectorFree(&list); + if (!r.b) { + path->len = 0; + return(path); + } + // TODO check null + setValB(path, &r); + return(path); +} + void printB(const btt b) { write(STDOUT_FILENO, b.b, b.len); } diff --git a/src/libsheepyBt.h b/src/libsheepyBt.h @@ -65,7 +65,7 @@ * * ``` * btt mystring = ccharB("Hello World!"); - * btt mystring2 = charB(strdup("Hello World!"), true); + * btt mystring2 = charB(strdup("Hello World!")); * btt mybuf = voidB("Binary", sizeof("Binary"), false); * createCharB(mystring3, "bytes", false); * // from another string: @@ -137,13 +137,13 @@ * * ``` * btt ss = ccharB("Hello World!"); - * vbtt lsspl = splitB(ss, " "); + * vbtt ll = splitB(ss, " "); * puts(""); * vectorForEach(&ll, e) { * printB(*e); * puts(""); * } - * vectorFree(&lsspl); + * vectorFree(&ll); * ``` * * @@ -186,7 +186,7 @@ * Empty btt: * btt s = {0}; * Assign C string: - * s = charB("a string", no); + * s = voidB("a string", no); * s.b = "asdasd"; * b.len = strlen(s.b); * @@ -378,6 +378,9 @@ btt *dupPB(btt *b); btt*: dupPB\ )(b) +// assign a to b, b is freed if it was allocated +btt *setValB(btt *b, btt *a); + // TODO: // ..lenB // ..freeB @@ -396,7 +399,7 @@ btt *dupPB(btt *b); // ..splitB // splitLenB // listFreeB -// join +// ..join // ..toCharB convert to char* // getB setB setBB set .b getBB get .b // @@ -451,6 +454,16 @@ void cleanUpFinishB(btt **val); // convert btt to char* allocated on heap char *toCharB(btt *b); +bool isEmptyB(const btt b); + +bool isEmptyPB(btt *b); + +bool isValidB(const btt b); + +bool isValidPB(btt *b); + +bool eqCharB(btt a, char *b); + // return a btt object with s appended to b // b doesn't need to have a heap allocated buffer #define appendB pushB @@ -837,6 +850,8 @@ btt slicePB(const btt *b, int64_t start, int64_t end); const btt*: slicePB\ )(b, start, end); +btt joinCharB(vbtt list, const char* delim); + // copyRngB // return a btt object with a heap allocated copy of b.b from start to end btt copyRngB(const btt b, int64_t start, int64_t end); @@ -864,6 +879,37 @@ btt trimPB(const btt *b); const btt*: trimPB\ )(b) +/** + * remove empty strings from list + * + * \param + * list + * \return + * list without empty strings + * empty list when list is empty + * unchanged list when list is NULL + * NULL error + */ +vbtt *bCompactB(vbtt *list); + +/** + * buffer size normalize path + * + * remove unecessary /, .. and . + * leading / is kept + * leading .. is kept + * leading . is removed + * + * '/../' becomes '/' + * + * \param + * path + * \return + * path modified path + * NULL when path is NULL + */ +btt *bNormalizePathB(btt *path); + void printB(const btt b); void printDebugB(const btt b); diff --git a/src/libsheepyObject.h b/src/libsheepyObject.h @@ -2895,6 +2895,11 @@ void finishManyOF(void *paramType, ...); char ***: iListShiftNSmashS \ )(self, obj) +// Delete an element in an array and move the elements after +// to fill the gap (delElemG deletes the element and creates a gap) +// the indices of the elements after are changed (-1) +#define delElG(self, idx) delG(self, idx, (idx)+1) + #define delO(self, start, end) (self)->f->del(self, start, end) #define delG(self, start, end) _Generic((self), \ smallDictt*: _Generic(start, \