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