Canonicalize an EXISTING path (collapse `..`, resolve links/junctions): * realpath on POSIX; a final path queried from an opened handle on Windows. * The previous Windows callers used the ANSI CRT (_access/_fullpath) on UTF-8 * input — locale-dependent by construction: on a CJK system codepage (e.g. * Big5) the UTF-8 bytes of a CJK path re-decode into different characters and * canonicalizati
| 962 | * only lexical and would let an allowed-root check follow a junction outside |
| 963 | * the root. Returns 0 when the path does not exist or cannot be resolved. */ |
| 964 | int cbm_canonical_path(const char *path, char *out, size_t out_sz) { |
| 965 | if (!path || !out || out_sz == 0) { |
| 966 | return 0; |
| 967 | } |
| 968 | #ifdef _WIN32 |
| 969 | wchar_t *wpath = cbm_path_to_wide(path); |
| 970 | if (!wpath) { |
| 971 | return 0; |
| 972 | } |
| 973 | HANDLE handle = CreateFileW(wpath, FILE_READ_ATTRIBUTES, |
| 974 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, |
| 975 | OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
| 976 | free(wpath); |
| 977 | if (handle == INVALID_HANDLE_VALUE) { |
| 978 | return 0; |
| 979 | } |
| 980 | DWORD needed = |
| 981 | GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); |
| 982 | /* MAXDWORD keeps the +1 below safe; calloc rejects an unrepresentable |
| 983 | * capacity * sizeof(wchar_t) allocation on narrower size_t targets. */ |
| 984 | if (needed == 0 || needed == MAXDWORD) { |
| 985 | (void)CloseHandle(handle); |
| 986 | return 0; |
| 987 | } |
| 988 | size_t capacity = (size_t)needed + 1; |
| 989 | wchar_t *wfull = calloc(capacity, sizeof(*wfull)); |
| 990 | if (!wfull) { |
| 991 | (void)CloseHandle(handle); |
| 992 | return 0; |
| 993 | } |
| 994 | DWORD n = GetFinalPathNameByHandleW(handle, wfull, (DWORD)capacity, |
| 995 | FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); |
| 996 | (void)CloseHandle(handle); |
| 997 | if (n == 0 || (size_t)n >= capacity) { |
| 998 | free(wfull); |
| 999 | return 0; |
| 1000 | } |
| 1001 | |
| 1002 | /* Preserve the conventional DOS/UNC form returned by the old API while |
| 1003 | * retaining the handle-based resolution. */ |
| 1004 | if (wcsncmp(wfull, L"\\\\?\\UNC\\", 8) == 0) { |
| 1005 | size_t tail_length = wcslen(wfull + 8); |
| 1006 | wmemmove(wfull + 2, wfull + 8, tail_length + 1); |
| 1007 | wfull[0] = L'\\'; |
| 1008 | wfull[1] = L'\\'; |
| 1009 | } else if (wcsncmp(wfull, L"\\\\?\\", 4) == 0) { |
| 1010 | size_t tail_length = wcslen(wfull + 4); |
| 1011 | wmemmove(wfull, wfull + 4, tail_length + 1); |
| 1012 | } |
| 1013 | char *utf8 = cbm_wide_to_utf8(wfull); |
| 1014 | free(wfull); |
| 1015 | if (!utf8) { |
| 1016 | return 0; |
| 1017 | } |
| 1018 | size_t len = strlen(utf8); |
| 1019 | if (len >= out_sz) { |
| 1020 | free(utf8); |
| 1021 | return 0; |