Skip to content

Commit 102e7a8

Browse files
committed
Bugfixes
- Bugfixes in path handling functions, especially around win32 drive letters and UCN paths - Fixed `ext_new_array` macro - Minor other bugfixes
1 parent c9aa4aa commit 102e7a8

2 files changed

Lines changed: 211 additions & 7 deletions

File tree

extlib.h

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
*
4444
* Changelog:
4545
*
46+
* v1.3.1:
47+
* - Bugfixes in path handling functions, especially around win32 drive letters and UCN paths
48+
* - Fixed `ext_new_array` macro
49+
* - Minor other bugfixes
50+
*
4651
* v1.3.0:
4752
* - New `StringSlice` functions: `ss_strip_prefix`, `ss_strip_suffix` (and `_cstr`
4853
* variants), `ss_eq_ignore_case`, `ss_cmp_ignore_case`, `ss_starts_with_ignore_case`,
@@ -522,7 +527,7 @@ typedef struct Ext_Allocator {
522527
// ext_clone:
523528
// Creates a copy of the provided pointer using `ext_memdup`
524529
#define ext_new(T) ext_alloc(sizeof(T))
525-
#define ext_new_array(T, count) ext_alloc(sizeof(int) * count)
530+
#define ext_new_array(T, count) ext_alloc(sizeof(T) * count)
526531
#define ext_delete(T, ptr) ext_free(ptr, sizeof(T))
527532
#define ext_delete_array(T, count, ptr) ext_free(ptr, sizeof(T) * count);
528533
#define ext_clone(T, ptr) ext_memdup(ptr, sizeof(T));
@@ -1956,7 +1961,7 @@ static void *ext_default_realloc(Ext_Allocator *a, void *ptr, size_t old_size, s
19561961
#else
19571962
(void)ptr;
19581963
(void)new_size;
1959-
return NULL
1964+
return NULL;
19601965
#endif
19611966
}
19621967

@@ -2668,8 +2673,9 @@ Ext_StringSlice ext_ss_strip_suffix_cstr(Ext_StringSlice ss, const char *suffix)
26682673
}
26692674

26702675
int ext_ss_cmp(Ext_StringSlice s1, Ext_StringSlice s2) {
2671-
size_t min_sz = s1.size < s2.size ? s1.size : s2.size;
2672-
return memcmp(s1.data, s2.data, min_sz);
2676+
if(s1.size < s2.size) return -1;
2677+
else if(s1.size > s2.size) return 1;
2678+
else return memcmp(s1.data, s2.data, s1.size);
26732679
}
26742680

26752681
bool ext_ss_eq(Ext_StringSlice s1, Ext_StringSlice s2) {
@@ -2746,12 +2752,75 @@ static bool ext__is_path_sep(char c) {
27462752
#endif
27472753
}
27482754

2755+
#ifdef EXT_WINDOWS
2756+
static bool ext__is_drive_letter(Ext_StringSlice path) {
2757+
if(path.size < 2) return false;
2758+
char c = path.data[0];
2759+
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && path.data[1] == ':';
2760+
}
2761+
2762+
static bool ext__is_unc_path(Ext_StringSlice path) {
2763+
return path.size >= 2 && ext__is_path_sep(path.data[0]) && ext__is_path_sep(path.data[1]);
2764+
}
2765+
2766+
// Find the UNC root (e.g., server and share from path)
2767+
// Returns the length of the root, or 0 if not a valid UNC path
2768+
static size_t ext__unc_root_length(Ext_StringSlice path) {
2769+
if(!ext__is_unc_path(path)) return 0;
2770+
size_t pos = 2; // Skip initial separators
2771+
2772+
// Special cases: extended-length and device paths
2773+
if(pos < path.size && path.data[pos] == '?') {
2774+
pos++; // Skip '?'
2775+
if(pos < path.size && ext__is_path_sep(path.data[pos])) {
2776+
pos++; // Skip separator
2777+
// Check for drive letter format
2778+
if(pos + 1 < path.size && path.data[pos + 1] == ':') {
2779+
return pos + 2; // Include drive letter and colon
2780+
}
2781+
// Check for UNC format
2782+
if(pos + 3 < path.size && (path.data[pos] == 'U' || path.data[pos] == 'u') &&
2783+
(path.data[pos + 1] == 'N' || path.data[pos + 1] == 'n') &&
2784+
(path.data[pos + 2] == 'C' || path.data[pos + 2] == 'c') &&
2785+
ext__is_path_sep(path.data[pos + 3])) {
2786+
pos += 4; // Skip "UNC" and separator
2787+
// Fall through to find server and share
2788+
}
2789+
}
2790+
} else if(pos < path.size && path.data[pos] == '.') {
2791+
pos++; // Skip '.'
2792+
if(pos < path.size && ext__is_path_sep(path.data[pos])) {
2793+
pos++; // Skip separator
2794+
// Device format - find next separator or end
2795+
while(pos < path.size && !ext__is_path_sep(path.data[pos])) {
2796+
pos++;
2797+
}
2798+
return pos;
2799+
}
2800+
}
2801+
2802+
// Standard UNC: find server name (up to next separator)
2803+
while(pos < path.size && !ext__is_path_sep(path.data[pos])) {
2804+
pos++;
2805+
}
2806+
if(pos >= path.size) return 0; // Invalid: no share name
2807+
2808+
pos++; // Skip separator after server
2809+
2810+
// Find share name (up to next separator or end)
2811+
while(pos < path.size && !ext__is_path_sep(path.data[pos])) {
2812+
pos++;
2813+
}
2814+
2815+
return pos;
2816+
}
2817+
#endif
2818+
27492819
Ext_StringSlice ext_ss_basename(Ext_StringSlice path) {
27502820
// Strip trailing separators
27512821
while(path.size > 0 && ext__is_path_sep(path.data[path.size - 1])) {
27522822
path.size--;
27532823
}
2754-
// Find last separator
27552824
for(size_t i = path.size; i > 0; i--) {
27562825
if(ext__is_path_sep(path.data[i - 1])) {
27572826
return ext_ss_cut(path, i);
@@ -2761,23 +2830,65 @@ Ext_StringSlice ext_ss_basename(Ext_StringSlice path) {
27612830
}
27622831

27632832
Ext_StringSlice ext_ss_dirname(Ext_StringSlice path) {
2833+
#ifdef EXT_WINDOWS
2834+
size_t unc_root = ext__unc_root_length(path);
2835+
if(unc_root > 0) {
2836+
// Strip trailing separators after UNC root
2837+
size_t end = path.size;
2838+
while(end > unc_root && ext__is_path_sep(path.data[end - 1])) {
2839+
end--;
2840+
}
2841+
for(size_t i = end; i > unc_root; i--) {
2842+
if(ext__is_path_sep(path.data[i - 1])) {
2843+
size_t dir_end = i - 1;
2844+
while(dir_end > unc_root && ext__is_path_sep(path.data[dir_end - 1])) {
2845+
dir_end--;
2846+
}
2847+
return ext_ss_trunc(path, dir_end);
2848+
}
2849+
}
2850+
2851+
// No separator after UNC root - return the root itself
2852+
return ext_ss_trunc(path, unc_root);
2853+
}
2854+
#endif
2855+
27642856
// Strip trailing separators (but keep at least one char for root paths)
27652857
size_t end = path.size;
27662858
while(end > 1 && ext__is_path_sep(path.data[end - 1])) {
27672859
end--;
27682860
}
2861+
27692862
// Find last separator
27702863
for(size_t i = end; i > 0; i--) {
27712864
if(ext__is_path_sep(path.data[i - 1])) {
2772-
// Strip trailing separators from result, but keep at least one for root "/"
27732865
size_t dir_end = i - 1;
27742866
while(dir_end > 0 && ext__is_path_sep(path.data[dir_end - 1])) {
27752867
dir_end--;
27762868
}
2777-
if(dir_end == 0) dir_end = 1; /* root "/" */
2869+
2870+
// Handle root paths
2871+
if(dir_end == 0) {
2872+
dir_end = 1; // Unix root "/"
2873+
}
2874+
#ifdef EXT_WINDOWS
2875+
// Windows: if we're at position 1 and have a drive letter, include the colon
2876+
else if(dir_end == 1 && path.size >= 2 && path.data[1] == ':') {
2877+
dir_end = 2; // Windows drive "C:"
2878+
}
2879+
#endif
27782880
return ext_ss_trunc(path, dir_end);
27792881
}
27802882
}
2883+
2884+
#ifdef EXT_WINDOWS
2885+
// If path is just a drive letter (e.g., "C:"), return it as-is
2886+
if(ext__is_drive_letter(path)) {
2887+
return ext_ss_trunc(path, 2);
2888+
}
2889+
#endif
2890+
2891+
// No separator found
27812892
return (Ext_StringSlice){0, path.data};
27822893
}
27832894

@@ -2795,7 +2906,19 @@ Ext_StringSlice ext_ss_extension(Ext_StringSlice path) {
27952906

27962907
void ext_sb_append_path(Ext_StringBuffer *sb, Ext_StringSlice component) {
27972908
if(sb->size > 0 && !ext__is_path_sep(sb->items[sb->size - 1])) {
2909+
#ifdef EXT_WINDOWS
2910+
// Use the same separator style as the existing path
2911+
char sep = '/';
2912+
for(size_t i = 0; i < sb->size; i++) {
2913+
if(sb->items[i] == '\\') {
2914+
sep = '\\';
2915+
break;
2916+
}
2917+
}
2918+
ext_sb_append_char(sb, sep);
2919+
#else
27982920
ext_sb_append_char(sb, '/');
2921+
#endif
27992922
}
28002923
ext_sb_append(sb, component.data, component.size);
28012924
}

test/test.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,87 @@ CTEST(sb, append_path) {
13651365
#endif
13661366
}
13671367

1368+
#ifdef EXT_WINDOWS
1369+
CTEST(slice, windows_drive_letters) {
1370+
// Basic drive letter paths
1371+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:\\file.txt")), SS("C:")));
1372+
ASSERT_TRUE(ss_eq(ss_dirname(SS("D:\\dir\\file.txt")), SS("D:\\dir")));
1373+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:/file.txt")), SS("C:")));
1374+
ASSERT_TRUE(ss_eq(ss_dirname(SS("E:/dir/file.txt")), SS("E:/dir")));
1375+
1376+
// Drive letter only
1377+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:")), SS("C:")));
1378+
ASSERT_TRUE(ss_eq(ss_dirname(SS("D:")), SS("D:")));
1379+
1380+
// Drive letter with trailing separator
1381+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:\\")), SS("C:")));
1382+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:/")), SS("C:")));
1383+
1384+
// Multiple levels
1385+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:\\Users\\test\\file.txt")), SS("C:\\Users\\test")));
1386+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:\\Users\\test")), SS("C:\\Users")));
1387+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:\\Users")), SS("C:")));
1388+
}
1389+
1390+
CTEST(slice, windows_unc_paths) {
1391+
// Standard UNC paths
1392+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\server\\share\\file.txt")), SS("\\\\server\\share")));
1393+
ASSERT_TRUE(
1394+
ss_eq(ss_dirname(SS("\\\\server\\share\\dir\\file.txt")), SS("\\\\server\\share\\dir")));
1395+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\server\\share")), SS("\\\\server\\share")));
1396+
1397+
// UNC path with trailing separator
1398+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\server\\share\\")), SS("\\\\server\\share")));
1399+
1400+
// Extended-length paths with drive letters
1401+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\?\\C:\\file.txt")), SS("\\\\?\\C:")));
1402+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\?\\C:\\dir\\file.txt")), SS("\\\\?\\C:\\dir")));
1403+
1404+
// Device paths
1405+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\.\\device\\file.txt")), SS("\\\\.\\device")));
1406+
ASSERT_TRUE(ss_eq(ss_dirname(SS("\\\\.\\device")), SS("\\\\.\\device")));
1407+
1408+
// Basename should work correctly with UNC paths
1409+
ASSERT_TRUE(ss_eq(ss_basename(SS("\\\\server\\share\\file.txt")), SS("file.txt")));
1410+
ASSERT_TRUE(ss_eq(ss_basename(SS("\\\\server\\share")), SS("share")));
1411+
}
1412+
1413+
CTEST(slice, windows_mixed_separators) {
1414+
// Mixed forward and backslashes
1415+
ASSERT_TRUE(ss_eq(ss_dirname(SS("C:\\Users/test\\file.txt")), SS("C:\\Users/test")));
1416+
ASSERT_TRUE(ss_eq(ss_basename(SS("C:\\Users/test\\file.txt")), SS("file.txt")));
1417+
1418+
// Extension should work with mixed separators
1419+
ASSERT_TRUE(ss_eq(ss_extension(SS("C:\\Users/test\\file.txt")), SS(".txt")));
1420+
}
1421+
1422+
CTEST(sb, windows_path_separator_consistency) {
1423+
// Test that sb_append_path uses consistent separators
1424+
StringBuffer sb = {0};
1425+
1426+
// Start with backslash style
1427+
sb_append_cstr(&sb, "C:\\Windows");
1428+
sb_append_path_cstr(&sb, "System32");
1429+
ASSERT_TRUE(memcmp(sb.items, "C:\\Windows\\System32", sb.size) == 0);
1430+
sb_free(&sb);
1431+
1432+
// Start with forward slash style
1433+
StringBuffer sb2 = {0};
1434+
sb_append_cstr(&sb2, "C:/Windows");
1435+
sb_append_path_cstr(&sb2, "System32");
1436+
ASSERT_TRUE(memcmp(sb2.items, "C:/Windows/System32", sb2.size) == 0);
1437+
sb_free(&sb2);
1438+
1439+
// Multiple appends should maintain style
1440+
StringBuffer sb3 = {0};
1441+
sb_append_cstr(&sb3, "C:\\Users");
1442+
sb_append_path_cstr(&sb3, "test");
1443+
sb_append_path_cstr(&sb3, "Documents");
1444+
ASSERT_TRUE(memcmp(sb3.items, "C:\\Users\\test\\Documents", sb3.size) == 0);
1445+
sb_free(&sb3);
1446+
}
1447+
#endif
1448+
13681449
typedef struct {
13691450
int key;
13701451
int value;

0 commit comments

Comments
 (0)