From 5087eded837666cf459514d4ef8740161899430a Mon Sep 17 00:00:00 2001 From: DCurrent Date: Wed, 10 Jun 2026 21:21:49 -0400 Subject: [PATCH] tools/borpak: prepare PAK tool for large-file support Clean up archive entry naming and replace ad-hoc integer fields with explicit PAK field types. Validate table offsets, entry sizes, file data ranges, and extraction paths before writing files. Also fix unterminated archive names, checked writes, scandir cleanup paths, and path traversal handling. This separates current 32-bit on-disk fields from local size and offset handling as groundwork for future large PAK file support. --- tools/borpak/source/borpak.c | 638 ++++++++++++++++++++++++---------- tools/borpak/source/scandir.c | 273 ++++++++++++--- tools/borpak/source/scandir.h | 34 +- tools/borpak/source/stristr.c | 142 +++++--- tools/borpak/source/stristr.h | 19 +- 5 files changed, 811 insertions(+), 295 deletions(-) diff --git a/tools/borpak/source/borpak.c b/tools/borpak/source/borpak.c index 65e230644..3ce08f21e 100644 --- a/tools/borpak/source/borpak.c +++ b/tools/borpak/source/borpak.c @@ -26,6 +26,8 @@ #include #include #include +#include + #ifdef WIN32 #include @@ -43,59 +45,136 @@ #define PATHSLASH '/' #endif - #define FNAMEMAX 1024 +#define PAK_UINT_MAX UINT32_MAX -#define FWRITE(X,Y,Z) if(fwrite(X, Y, 1, Z) != 1) write_err(); - +typedef uint32_t pak_u32; -int put_file(FILE *fd, char *fname); -void get_file(FILE *fd, char *fname, u_int off, u_int size); -u_int fdrinum(FILE *fd, int size); -void fdwinum(FILE *fd, u_int num, int size); -int recursive_dir(FILE *fd, char *filedir); +/* +* 2026-06-10 Damon V. Caskey +* +* Not used, for future large pak file +* support. +*/ +typedef uint64_t pak_offset_t; +typedef uint64_t pak_size_t; + +void file_write(const void *buffer, size_t size, FILE *file_object); +int pack_file(FILE *pak_file_object, char *file_path); +void extract_file(FILE *pak_file_object, char *file_path, pak_u32 data_offset, pak_u32 data_size); +pak_u32 read_le_uint(FILE *pak_file_object, int bits); +void write_le_uint(FILE *pak_file_object, pak_u32 num, int bits); +int pack_directory(FILE *pak_file_object, char *directory_path); void write_err(void); void std_err(void); void winpause(void); - +pak_u32 pak_tell32(FILE *pak_file_object); +int is_unsafe_path(const char *name); typedef struct { - u_int pns; - u_int off; - u_int size; + pak_u32 record_size; + pak_u32 data_offset; + pak_u32 data_size; char *name; -} pn_t; +} pak_entry_t; -struct pnp_t { - pn_t pn; - struct pnp_t *next; +struct pak_entry_node_t { + pak_entry_t entry; + struct pak_entry_node_t *next; }; +struct pak_entry_node_t *pak_entries = NULL; +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Writes a fixed-size block to a file. +* Exits through write_err() if the write fails. +*/ +void file_write(const void *buffer, size_t size, FILE *file_object) { + if(fwrite(buffer, size, 1, file_object) != 1) { + write_err(); + } +} + +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Returns the current file position as a 32-bit +* archive offset. The PAK format stores offsets +* in 32-bit little-endian fields. +*/ +pak_u32 pak_tell32(FILE *pak_file_object) { + long pos; -struct pnp_t *pnp = NULL; + pos = ftell(pak_file_object); + if(pos < 0) { + std_err(); + } + + if((unsigned long)pos > PAK_UINT_MAX) { + std_err(); + } + return (pak_u32)pos; +} + +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Rejects archive names that would escape the +* extraction directory. PAK entries should always +* be relative paths. +*/ +int is_unsafe_path(const char *name) { + + if(name[0] == '/' || name[0] == '\\') { + return 1; + } + + if(strstr(name, "../") || strstr(name, "..\\")) { + return 1; + } + +#ifdef WIN32 + if(strchr(name, ':')) { + return 1; + } +#endif + + return 0; +} int main(int argc, char *argv[]) { - struct pnp_t *ps, - *psfree; - pn_t pn; - FILE *fd; - u_int off; - int i, - len, - list = 0, - build = 0, - packver = 0, - filez = 0; - char pack[4], - curdir[512], - *dir = NULL, - *patt = NULL, - *fname; + + struct pak_entry_node_t *entry_node; + struct pak_entry_node_t *entry_node_free; + + pak_entry_t entry; + FILE *pak_file_object; + pak_u32 table_offset; + pak_u32 table_end_offset; + pak_u32 table_start_offset; + int i; + size_t name_length; + int list = 0; + int build = 0; + pak_u32 packver = 0; + int file_count = 0; + int fgetc_result = 0; + + char pack[4]; + char curdir[512]; + char *input_dir = NULL; + char *pattern = NULL; + char *pak_filename; setbuf(stdout, NULL); + #ifdef WIN32 atexit(winpause); #endif @@ -128,218 +207,366 @@ int main(int argc, char *argv[]) { argc--; for(i = 1; i < argc; i++) { - if(argv[i][0] != '-') break; + + if(argv[i][0] != '-') { + break; + } + switch(argv[i][1]) { - case 'd': dir = argv[++i]; break; - case 'b': build = 1; break; - case 'l': list = 1; break; - case 'p': patt = argv[++i]; break; + case 'd': + + if(i + 1 >= argc) { + printf("\nError: missing argument for -d option\n\n"); + exit(1); + } + + input_dir = argv[++i]; break; + + case 'b': + + build = 1; break; + case 'l': + + list = 1; break; + case 'p': + + if(i + 1 >= argc) { + printf("\nError: missing argument for -p option\n\n"); + exit(1); + } + + pattern = argv[++i]; break; default: { printf("\nError: wrong command-line argument (%s)\n\n", argv[i]); exit(1); } break; } } - fname = argv[argc]; + pak_filename = argv[argc]; if(build) { - if(!dir) { + if(!input_dir) { printf("\n" "Error: please specify the files directory with the -d option and don't specify\n" " the current\n\n"); exit(1); } - printf("- create file: %s\n", fname); - printf("- directory: %s\n\n", dir); - fd = fopen(fname, "rb"); - if(fd) { - fclose(fd); + printf("- create file: %s\n", pak_filename); + printf("- directory: %s\n\n", input_dir); + pak_file_object = fopen(pak_filename, "rb"); + if(pak_file_object) { + fclose(pak_file_object); printf("- a file with the same name already exists, overwrite? (y/N)\n "); - fflush(stdin); - if(tolower(fgetc(stdin)) != 'y') exit(1); + + fgetc_result = fgetc(stdin); + if(fgetc_result == EOF || tolower((unsigned char)fgetc_result) != 'y') { + exit(1); + } } - fd = fopen(fname, "wb"); - if(!fd) std_err(); + pak_file_object = fopen(pak_filename, "wb"); - FWRITE("PACK", 4, fd); - fdwinum(fd, packver, 32); + if(!pak_file_object){ + std_err(); + } + + file_write("PACK", 4, pak_file_object); + write_le_uint(pak_file_object, packver, 32); printf( " offset size filename\n" "--------------------------------\n"); - if(recursive_dir(fd, dir) < 0) { + if(pack_directory(pak_file_object, input_dir) < 0) { printf("\nError: an error occurred during the directory scanning\n"); goto quit; } - // fflush(fd); - off = ftell(fd); - printf("- files info offset: %08x\n", off); - - ps = pnp; - while(ps) { - fdwinum(fd, ps->pn.pns, 32); - fdwinum(fd, ps->pn.off, 32); - fdwinum(fd, ps->pn.size, 32); - FWRITE(ps->pn.name, ps->pn.pns - 12, fd); - - psfree = ps; - ps = ps->next; - free(psfree->pn.name); - free(psfree); - filez++; + // fflush(pak_file_object); + table_offset = pak_tell32(pak_file_object); + printf("- files info offset: %08x\n", (unsigned int)table_offset); + + entry_node = pak_entries; + while(entry_node) { + write_le_uint(pak_file_object, entry_node->entry.record_size, 32); + write_le_uint(pak_file_object, entry_node->entry.data_offset, 32); + write_le_uint(pak_file_object, entry_node->entry.data_size, 32); + file_write(entry_node->entry.name, entry_node->entry.record_size - 12, pak_file_object); + + entry_node_free = entry_node; + entry_node = entry_node->next; + free(entry_node_free->entry.name); + free(entry_node_free); + file_count++; } - fdwinum(fd, off, 32); + write_le_uint(pak_file_object, table_offset, 32); } else { - printf("- open file: %s\n", fname); - fd = fopen(fname, "rb"); - if(!fd) std_err(); + printf("- open file: %s\n", pak_filename); + pak_file_object = fopen(pak_filename, "rb"); + + if(!pak_file_object){ + std_err(); + } + + if(input_dir) { + printf("- change directory: %s\n", input_dir); + + if(chdir(input_dir) < 0) { + std_err(); + } + } + + if(!fread(pack, 4, 1, pak_file_object)){ + goto quit; + } + + if(memcmp(pack, "PACK", 4)){ + goto quit; + } + + packver = read_le_uint(pak_file_object, 32); + + if(fseek(pak_file_object, -4, SEEK_END) < 0) { + std_err(); + } + + /* + * The final 4 bytes store the offset of the file table. + * The table itself ends immediately before that footer. + */ + table_end_offset = pak_tell32(pak_file_object); + table_offset = read_le_uint(pak_file_object, 32); + + if(table_offset < 8 || table_offset > table_end_offset) { + printf("\nError: malformed PAK file table offset\n"); + goto quit; + } + + table_start_offset = table_offset; - if(dir) { - printf("- change directory: %s\n", dir); - if(chdir(dir) < 0) std_err(); + if(fseek(pak_file_object, table_offset, SEEK_SET) < 0) { + std_err(); } - if(!fread(pack, 4, 1, fd)) goto quit; - if(memcmp(pack, "PACK", 4)) goto quit; + while(table_offset < table_end_offset) { + entry.record_size = read_le_uint(pak_file_object, 32); + entry.data_offset = read_le_uint(pak_file_object, 32); + entry.data_size = read_le_uint(pak_file_object, 32); - packver = fdrinum(fd, 32); + if(entry.record_size <= 12) { + printf("\nError: malformed PAK file table entry\n"); + goto quit; + } - if(fseek(fd, -4, SEEK_END) < 0) std_err(); + if(entry.record_size > table_end_offset - table_offset) { + printf("\nError: malformed PAK file table\n"); + goto quit; + } - off = fdrinum(fd, 32); - if(fseek(fd, off, SEEK_SET) < 0) std_err(); + name_length = entry.record_size - 12; - for(;;) { - pn.pns = fdrinum(fd, 32); - pn.off = fdrinum(fd, 32); - pn.size = fdrinum(fd, 32); + entry.name = malloc(name_length + 1); + + if(!entry.name) { + std_err(); + } + - len = pn.pns - 12; - if(len <= 0) break; - pn.name = malloc(len + 1); - if(!pn.name) std_err(); + if(fread(entry.name, name_length, 1, pak_file_object) != 1) { + free(entry.name); + std_err(); + } - if(!fread(pn.name, len, 1, fd)) break; + /* + * Archive names are stored as raw bytes. Add the terminator + * before using the name with normal C string functions. + */ + entry.name[name_length] = '\0'; - if(!patt || (patt && stristr(pn.name, patt))) { - printf(" %s\n", pn.name); + if(!pattern || (pattern && stristr(entry.name, pattern))) { + printf(" %s\n", entry.name); if(!list) { - if(strstr(pn.name, "../") || strstr(pn.name, "..\\") || pn.name[0] == '/' || pn.name[0] == '\\') { - printf(" skipping %s: path traversal detected\n", pn.name); + + /* + * Verify safe extraction before writing any files. + * This prevents malicious or malformed archive entries + * from escaping the extraction directory. + */ + if(is_unsafe_path(entry.name)) { + printf(" skipping %s: path traversal detected\n", entry.name); } else { - get_file(fd, pn.name, pn.off, pn.size); + + if(entry.data_offset > table_start_offset || + entry.data_size > table_start_offset - entry.data_offset) { + printf("\nError: malformed PAK file data range: %s\n", entry.name); + free(entry.name); + goto quit; + } + + extract_file(pak_file_object, entry.name, entry.data_offset, entry.data_size); } } - filez++; + file_count++; } - free(pn.name); - off += pn.pns; - if(fseek(fd, off, SEEK_SET) < 0) std_err(); + free(entry.name); + table_offset += entry.record_size; + if(fseek(pak_file_object, table_offset, SEEK_SET) < 0){ + std_err(); + } } } quit: - fclose(fd); - printf("- finished: %d files\n", filez); + fclose(pak_file_object); + printf("- finished: %d files\n", file_count); printf("- current directory: %s\n", getcwd(curdir, sizeof(curdir))); return 0; } +int pack_file(FILE *pak_file_object, char *file_path) { - -int put_file(FILE *fd, char *fname) { - struct pnp_t *ps; + struct pak_entry_node_t *entry_node; FILE *fdi; - int len; - u_int off, - size; - u_char buff[8192]; - char *p; - - // printf(" %s\r", fname); - fdi = fopen(fname, "rb"); - if(!fdi) std_err(); + size_t bytes_read; + size_t name_length; + pak_u32 data_offset; + size_t data_size; + + unsigned char buff[8192]; + char *path; + + // printf(" %s\r", file_path); + fdi = fopen(file_path, "rb"); + if(!fdi){ + std_err(); + } - if(fname[0] == '.') fname += 2; /* skip .\ */ + /* + * Skip leading "./" if present. + */ + if(file_path[0] == '.' && (file_path[1] == '/' || file_path[1] == '\\')) { + file_path += 2; + } - for(p = fname; *p; p++) { // WIN mode - if(*p == '/') { - *p = '\\'; + for(path = file_path; *path; path++) { // WIN mode + if(*path == '/') { + *path = '\\'; } /*else { - *p = toupper(*p); + *path = toupper(*path); } */ } - // fflush(fd); - off = ftell(fd); + // fflush(pak_file_object); + data_offset = pak_tell32(pak_file_object); - for(size = 0; (len = fread(buff, 1, sizeof(buff), fdi)); size += len) { - FWRITE(buff, len, fd); + data_size = 0; + + while((bytes_read = fread(buff, 1, sizeof(buff), fdi)) != 0) { + if(data_size > PAK_UINT_MAX - bytes_read) { + printf("\nError: file too large for current PAK format: %s\n", file_path); + exit(1); + } + + file_write(buff, bytes_read, pak_file_object); + data_size += bytes_read; + } + + if(ferror(fdi)) { + std_err(); } fclose(fdi); - for(ps = pnp; ps && ps->next; ps = ps->next); + for(entry_node = pak_entries; entry_node && entry_node->next; entry_node = entry_node->next); + + if(entry_node) { + entry_node->next = malloc(sizeof(struct pak_entry_node_t)); + + if(!entry_node->next) { + std_err(); + } + entry_node = entry_node->next; - if(ps) { - ps->next = malloc(sizeof(struct pnp_t)); - if(!ps->next) std_err(); - ps = ps->next; } else { - pnp = malloc(sizeof(struct pnp_t)); - if(!pnp) std_err(); - ps = pnp; + pak_entries = malloc(sizeof(struct pak_entry_node_t)); + + if(!pak_entries) { + std_err(); + } + entry_node = pak_entries; } - ps->next = NULL; + entry_node->next = NULL; - len = strlen(fname) + 1; - ps->pn.pns = 12 + len; - ps->pn.off = off; - ps->pn.size = size; - ps->pn.name = malloc(len); - if(!ps->pn.name) std_err(); - memcpy(ps->pn.name, fname, len); + name_length = strlen(file_path) + 1; - printf(" %08x %10u %s\n", off, size, fname); - return(0); -} + if(name_length > PAK_UINT_MAX - 12) { + printf("\nError: file name too long for current PAK format: %s\n", file_path); + exit(1); + } + entry_node->entry.record_size = (pak_u32)(12 + name_length); + entry_node->entry.data_offset = data_offset; + entry_node->entry.data_size = (pak_u32)data_size; + entry_node->entry.name = malloc(name_length); + + if(!entry_node->entry.name) { + std_err(); + } + + memcpy(entry_node->entry.name, file_path, name_length); + printf(" %08x %10zu %s\n", (unsigned int)data_offset, data_size, file_path); + return(0); +} -void get_file(FILE *fd, char *fname, u_int off, u_int size) { +void extract_file(FILE *pak_file_object, char *file_path, pak_u32 data_offset, pak_u32 data_size) { FILE *fdo; - int rlen, - len; - u_char buff[8192]; - char *p; - - if(fseek(fd, off, SEEK_SET) < 0) std_err(); - - for(p = fname; *p; p++) { - if((*p == '\\') || (*p == '/')) { - *p = 0; - MKDIR(fname); - *p = PATHSLASH; + size_t chunk_size; + size_t bytes_read; + size_t bytes_remaining; + unsigned char buff[8192]; + char *path; + + if(fseek(pak_file_object, data_offset, SEEK_SET) < 0){ + std_err(); + } + + for(path = file_path; *path; path++) { + if((*path == '\\') || (*path == '/')) { + *path = 0; + MKDIR(file_path); + *path = PATHSLASH; } else { - *p = tolower(*p); + *path = (char)tolower((unsigned char)*path); } } - fdo = fopen(fname, "wb"); - if(!fdo) std_err(); + fdo = fopen(file_path, "wb"); + if(!fdo){ + std_err(); + } + + bytes_remaining = data_size; - for(rlen = sizeof(buff); size; size -= len) { - if(size < rlen) rlen = size; - len = fread(buff, 1, rlen, fd); - if(!len) break; - FWRITE(buff, len, fdo); + while(bytes_remaining) { + chunk_size = sizeof(buff); + + if(bytes_remaining < chunk_size) { + chunk_size = bytes_remaining; + } + + bytes_read = fread(buff, 1, chunk_size, pak_file_object); + + if(bytes_read == 0) { + std_err(); + } + + file_write(buff, bytes_read, fdo); + bytes_remaining -= bytes_read; } fclose(fdo); @@ -347,66 +574,96 @@ void get_file(FILE *fd, char *fname, u_int off, u_int size) { -u_int fdrinum(FILE *fd, int size) { - u_int num; +pak_u32 read_le_uint(FILE *pak_file_object, int bits) { + pak_u32 num; int i; - u_char tmp[sizeof(u_int)]; + int bytes; + unsigned char tmp[sizeof(pak_u32)]; + + bytes = bits >> 3; + + if(fread(tmp, bytes, 1, pak_file_object) != 1) { + std_err(); + } - size >>= 3; - fread(tmp, size, 1, fd); - for(num = i = 0; i < size; i++) { - num |= (tmp[i] << (i << 3)); + for(num = i = 0; i < bytes; i++) { + num |= ((pak_u32)tmp[i] << (i << 3)); } + return(num); } -void fdwinum(FILE *fd, u_int num, int size) { +void write_le_uint(FILE *pak_file_object, pak_u32 num, int bits) { int i; - u_char tmp[sizeof(u_int)]; - - size >>= 3; - for(i = 0; i < size; i++) { + int bytes; + unsigned char tmp[sizeof(pak_u32)]; + + bytes = bits >> 3; + for(i = 0; i < bytes; i++) { tmp[i] = (num >> (i << 3)) & 0xff; } - FWRITE(tmp, size, fd); + file_write(tmp, bytes, pak_file_object); } -int recursive_dir(FILE *fd, char *filedir) { +int pack_directory(FILE *pak_file_object, char *directory_path) { char tcDir[FNAMEMAX]; struct stat xstat; struct dirent **namelist; int n, i; + int written; - n = scandir(filedir, &namelist, NULL, alphasort); + n = scandir(directory_path, &namelist, NULL, alphasort); if(n < 0) { - if(stat(filedir, &xstat) < 0) { - printf("**** %s", filedir); + + if(stat(directory_path, &xstat) < 0) { + printf("**** %s", directory_path); std_err(); } - if(put_file(fd, filedir) < 0) return(-1); + + if(pack_file(pak_file_object, directory_path) < 0) { + return(-1); + } + } else { for(i = 0; i < n; i++) { // Changed by Plombo - if(!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) continue; + + /* + * scandir() allocates every returned entry, including "." and "..". + * Free skipped entries before continuing. + */ + if(!strcmp(namelist[i]->d_name, ".") || !strcmp(namelist[i]->d_name, "..")) { + free(namelist[i]); + continue; + } + + /* + * Construct the full path for the current entry. + * Check for path length overflow. + */ + - snprintf( - tcDir, - sizeof(tcDir), - "%s/%s", - filedir, - namelist[i]->d_name); + written = snprintf(tcDir, sizeof(tcDir), "%s/%s", directory_path, namelist[i]->d_name); + if(written < 0 || (size_t)written >= sizeof(tcDir)) { + printf("\nError: path too long: %s/%s\n", directory_path, namelist[i]->d_name); + goto quit; + } if(stat(tcDir, &xstat) < 0) { printf("**** %s", tcDir); std_err(); } if(S_ISDIR(xstat.st_mode)) { - if(recursive_dir(fd, tcDir) < 0) goto quit; + if(pack_directory(pak_file_object, tcDir) < 0) { + goto quit; + } } else { - if(put_file(fd, tcDir) < 0) goto quit; + if(pack_file(pak_file_object, tcDir) < 0) { + goto quit; + } } free(namelist[i]); } @@ -436,7 +693,8 @@ void std_err(void) { exit(1); } +#ifdef WIN32 void winpause(void) { system("pause"); } - +#endif diff --git a/tools/borpak/source/scandir.c b/tools/borpak/source/scandir.c index 01b7aa3d8..3d5c16497 100644 --- a/tools/borpak/source/scandir.c +++ b/tools/borpak/source/scandir.c @@ -12,60 +12,249 @@ ******************************************************************************** * * $Id: scandir.c,v 1.3 2003/11/07 11:15:53 jrh Exp $ -* $Log: scandir.c,v $ -* Revision 1.3 2003/11/07 11:15:53 jrh -* Release 2.4 * -* Revision 1.2 2000/12/10 15:37:02 jrh -* Release 2.3 -* -* Revision 1.1 1999/05/24 01:29:43 jrh -* Initial revision +* Caskey, Damon V. +* 2026-06-10 * +* Reworked to catch integer overflow, add error handling, +* and document internal behavior. Also added a simple insertion +* sort to avoid casting the caller's comparison callback +* to qsort's different function pointer type. */ -#include -#include -#include -#include -// This function is for Windows only, Linux already supports it +#if defined(_WIN32) || defined(WIN32) + +#include "scandir.h" + +#include +#include +#include +#include +#include -int scandir(const char *dir, struct dirent ***namelist, - int (*select)(const struct dirent *), - int (*compar)(const struct dirent **, const struct dirent **)) +/* +* Frees a partially or fully allocated scandir result list. +* Used on error paths so previously copied entries do not leak. +*/ +static void scandir_free_entries(struct dirent **namelist, size_t count) { - DIR *d; - struct dirent *entry; - register int i=0; - size_t entrysize; + size_t i; - if ((d=opendir(dir)) == NULL) - return(-1); + if (namelist == NULL) + { + return; + } - *namelist=NULL; - while ((entry=readdir(d)) != NULL) + for (i = 0; i < count; i++) { - if (select == NULL || (select != NULL && (*select)(entry))) - { - *namelist=(struct dirent **)realloc((void *)(*namelist), - (size_t)((i+1)*sizeof(struct dirent *))); - if (*namelist == NULL) return(-1); - entrysize=sizeof(struct dirent)-sizeof(entry->d_name)+strlen(entry->d_name)+1; - (*namelist)[i]=(struct dirent *)malloc(entrysize); - if ((*namelist)[i] == NULL) return(-1); - memcpy((*namelist)[i], entry, entrysize); - i++; + free(namelist[i]); + } + + free(namelist); +} + +/* +* Calculates the number of bytes needed to copy a +* directory entry. The Windows dirent shim uses a +* fixed-size structure, so copy the whole entry. +* +* Caskey, Damon V. +* 2026-06-10 +* +* Simplified to return the fixed size of struct dirent since +* the Windows shim does not support variable-length entries. +* Whoever wrote the original is a lot smarter than I am, but +* it assumes d_name is the final field in every dirent layout. +* For a packing tool, I don't think the optimization is worth +* the risk of breaking on some platform's dirent implementation. +*/ +static size_t scandir_entry_size(const struct dirent *entry) { + (void)entry; + + return sizeof(struct dirent); +} + +/* +* Sorts the collected entries using the scandir-compatible comparison callback. +* This avoids casting the callback to qsort's different function pointer type. +*/ +static void scandir_sort_entries(struct dirent **entries, + size_t count, + int (*compar)(const struct dirent **, + const struct dirent **)) +{ + size_t i; + + if (entries == NULL || compar == NULL || count < 2) { + return; + } + + for (i = 1; i < count; i++) { + struct dirent *current = entries[i]; + size_t j = i; + + while (j > 0) { + + const struct dirent *left = entries[j - 1]; + const struct dirent *right = current; + + if (compar(&left, &right) <= 0) + { + break; + } + + entries[j] = entries[j - 1]; + j--; } + + entries[j] = current; } - if (closedir(d)) return(-1); - if (i == 0) return(-1); - if (compar != NULL) - qsort((void *)(*namelist), (size_t)i, sizeof(struct dirent *), compar); - - return(i); } -int alphasort(const struct dirent **a, const struct dirent **b) +/* +* Windows compatibility implementation of POSIX scandir(). +* Reads a directory, optionally filters entries, copies matches, and sorts them. +*/ +int scandir(const char *dir, + struct dirent ***namelist, + int (*filter)(const struct dirent *), + int (*compar)(const struct dirent **, + const struct dirent **)) { - return(strcmp((*a)->d_name, (*b)->d_name)); + DIR *directory; + struct dirent *entry; + struct dirent **entries; + struct dirent **resized_entries; + size_t count; + size_t entry_size; + int saved_errno; + + /* + * Caller must provide a valid output pointer. + * The output is initialized early so callers never see stale data. + */ + if (namelist == NULL) { + errno = EINVAL; + return -1; + } + + *namelist = NULL; + + directory = opendir(dir); + if (directory == NULL) { + return -1; + } + + entries = NULL; + count = 0; + + for (;;){ + + /* + * readdir() returns NULL both at end-of-directory and on error. + * Clearing errno lets us distinguish those two cases. + */ + errno = 0; + entry = readdir(directory); + + if (entry == NULL) { + if (errno != 0) { + goto error; + } + + break; + } + + /* + * Skip entries rejected by the caller's filter function. + */ + if (filter != NULL && !filter(entry)) { + continue; + } + + /* + * Prevent integer overflow before growing the result array. + * scandir() returns int, so count also needs to stay in range. + */ + if (count >= (size_t)INT_MAX || + count + 1 > ((size_t)-1) / sizeof(*entries)) { + errno = ENOMEM; + goto error; + } + + resized_entries = (struct dirent **)realloc(entries, + (count + 1) * sizeof(*entries)); + + if (resized_entries == NULL) { + errno = ENOMEM; + goto error; + } + + entries = resized_entries; + entry_size = scandir_entry_size(entry); + + /* + * readdir() reuses its internal buffer, so every accepted entry + * must be copied before the next call to readdir(). + */ + entries[count] = (struct dirent *)malloc(entry_size); + + if (entries[count] == NULL) { + errno = ENOMEM; + goto error; + } + + memcpy(entries[count], entry, entry_size); + count++; + } + + /* + * closedir() can fail, so keep the regular cleanup path available. + */ + if (closedir(directory) != 0) { + directory = NULL; + + if (errno == 0) { + errno = EIO; + } + + goto error; + } + + directory = NULL; + + scandir_sort_entries(entries, count, compar); + + /* + * A directory with no selected entries is a successful scan. + * The caller receives NULL and a return value of 0. + */ + *namelist = entries; + + return (int)count; + +error: + saved_errno = errno; + + if (directory != NULL) { + closedir(directory); + directory = NULL; + } + + scandir_free_entries(entries, count); + *namelist = NULL; + + errno = saved_errno != 0 ? saved_errno : ENOMEM; + + return -1; } + +/* +* Compares two directory entries by filename. +* Used as the default alphabetical sort callback for this scandir shim. +*/ +int alphasort(const struct dirent **a, const struct dirent **b) { + return strcmp((*a)->d_name, (*b)->d_name); +} + +#endif /* defined(_WIN32) || defined(WIN32) */ \ No newline at end of file diff --git a/tools/borpak/source/scandir.h b/tools/borpak/source/scandir.h index e4e54092c..86c42edf8 100644 --- a/tools/borpak/source/scandir.h +++ b/tools/borpak/source/scandir.h @@ -1,5 +1,33 @@ -int scandir(const char *dir, struct dirent ***namelist, - int (*select)(const struct dirent *), - int (*compar)(const struct dirent **, const struct dirent **)); +#ifndef BORPAK_SCANDIR_H +#define BORPAK_SCANDIR_H 1 + +#include + +#if defined(_WIN32) || defined(WIN32) + +#ifdef __cplusplus +extern "C" { +#endif + +/* +* Scans a directory and returns an allocated list of matching entries. +* The caller owns the returned array and each entry inside it. +*/ +int scandir(const char *dir, + struct dirent ***namelist, + int (*filter)(const struct dirent *), + int (*compar)(const struct dirent **, + const struct dirent **)); + +/* +* Sort callback that compares directory entries by filename. +*/ int alphasort(const struct dirent **a, const struct dirent **b); +#ifdef __cplusplus +} +#endif + +#endif /* defined(_WIN32) || defined(WIN32) */ + +#endif /* BORPAK_SCANDIR_H */ \ No newline at end of file diff --git a/tools/borpak/source/stristr.c b/tools/borpak/source/stristr.c index 1b2178675..afca65018 100644 --- a/tools/borpak/source/stristr.c +++ b/tools/borpak/source/stristr.c @@ -1,86 +1,116 @@ /* ** Designation: StriStr ** -** Call syntax: char *stristr(char *String, char *Pattern) +** Call syntax: char *stristr(const char *haystack, const char *needle) ** ** Description: This function is an ANSI version of strstr() with ** case insensitivity. ** -** Return item: char *pointer if Pattern is found in String, else -** pointer to 0 +** Return item: char *pointer to the first match if needle is found +** in haystack, else NULL ** ** Rev History: 16/07/97 Greg Thayer Optimized ** 07/04/95 Bob Stout ANSI-fy ** 02/03/94 Fred Cole Original ** 09/01/03 Bob Stout Bug fix (lines 40-41) per Fred Bulback +** 2026-06-10 Damon V. Caskey Matched prototypes, fixed empty-pattern handling, +** made ctype use safe, and documented internals. ** ** Hereby donated to public domain. */ -#include -#include -#include -#define NUL '\0' +#include "stristr.h" -typedef unsigned int uint; +#include +#include -#if defined(__cplusplus) && __cplusplus - extern "C" { +#ifdef TEST +#include #endif -char *stristr(const char *String, const char *Pattern) +#define NUL '\0' + +/* +* Converts a character to uppercase safely for ctype functions. +* The cast avoids undefined behavior when char is signed. +*/ +static int stristr_toupper(char c) { - char *pptr, *sptr, *start; - - for (start = (char *)String; *start != NUL; start++) - { - /* find start of pattern in string */ - for ( ; ((*start!=NUL) && (toupper(*start) != toupper(*Pattern))); start++) - ; - if (NUL == *start) - return NULL; - - pptr = (char *)Pattern; - sptr = (char *)start; - - while (toupper(*sptr) == toupper(*pptr)) - { - sptr++; - pptr++; - - /* if end of pattern then pattern was found */ - - if (NUL == *pptr) - return (start); - } - } - return NULL; + return toupper((unsigned char)c); } -#if defined(__cplusplus) && __cplusplus - } -#endif +/* +* Searches haystack for needle without matching letter case. +* Mirrors strstr() behavior, including returning haystack for an empty needle. +*/ +char *stristr(const char *haystack, const char *needle) +{ + const char *start; + const char *haystack_cursor; + const char *needle_cursor; + + /* + * Match strstr() behavior for an empty search pattern. + */ + if (*needle == NUL) + { + return (char *)haystack; + } + + for (start = haystack; *start != NUL; start++) + { + /* + * Move to the next possible first-character match. + */ + if (stristr_toupper(*start) != stristr_toupper(*needle)) + { + continue; + } + + haystack_cursor = start; + needle_cursor = needle; + + /* + * Compare both strings from the candidate position. + */ + while (*haystack_cursor != NUL && + *needle_cursor != NUL && + stristr_toupper(*haystack_cursor) == stristr_toupper(*needle_cursor)) + { + haystack_cursor++; + needle_cursor++; + } + + /* + * Reaching the end of needle means the full pattern matched. + */ + if (*needle_cursor == NUL) + { + return (char *)start; + } + } + + return NULL; +} #ifdef TEST int main(void) { - int i; - char *buffer[2] = {"heLLo, HELLO, hello, hELLo, HellO", "Hell"}; - char *sptr; - - for (i = 0; i < 2; ++i) - { - printf("\nTest string=\"%s\"\n", sptr = buffer[i]); - while (0 != (sptr = stristr(sptr, "hello"))) - { - printf("Testing %s:\n", sptr); - printf("Found %5.5s!\n", sptr++); - } - } - - return(0); -} + int i; + const char *buffer[2] = {"heLLo, HELLO, hello, hELLo, HellO", "Hell"}; + const char *sptr; + + for (i = 0; i < 2; ++i) { + printf("\nTest string=\"%s\"\n", sptr = buffer[i]); -#endif /* TEST */ + while ((sptr = stristr(sptr, "hello")) != NULL) { + printf("Testing %s:\n", sptr); + printf("Found %5.5s!\n", sptr++); + } + } + + return 0; +} +#endif /* TEST */ \ No newline at end of file diff --git a/tools/borpak/source/stristr.h b/tools/borpak/source/stristr.h index bbf0858ca..2c0f9092e 100644 --- a/tools/borpak/source/stristr.h +++ b/tools/borpak/source/stristr.h @@ -8,7 +8,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License @@ -18,10 +18,21 @@ http://www.gnu.org/licenses/gpl.txt */ -#ifndef STRISTRI_H -#define STRISTRI_H +#ifndef STRISTR_H +#define STRISTR_H -char* stristr(char* haystack, char* needle); +#if defined(__cplusplus) && __cplusplus +extern "C" { +#endif + +/* +* Searches haystack for needle without matching letter case. +* Returns a pointer to the first match, or NULL if no match is found. +*/ +char *stristr(const char *haystack, const char *needle); +#if defined(__cplusplus) && __cplusplus +} #endif +#endif /* STRISTR_H */ \ No newline at end of file