From d0e1d360098b5fe2f03e5088d326f84ee344eca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Fri, 27 Oct 2023 15:46:13 +0200 Subject: [PATCH] [SDK:LIB] Import PathCch* functions from Wine (#5842) CORE-12686 Isolate PathCch* functions from Wine implementation in kernelbase (which are mixed with a ton of unrelated stuff). These functions are compiled into a pathcch.lib library, similarly to the one in the official MS PSDK. Excepting that here, their actual implementation is in the library. This contrasts with the one in the MS PSDK, which is an import library to an apiset DLL. The pathcch.h header is an original one, that contains SAL annotations and descriptive parameter names, based on the MinGW and MS PSDK headers. Wine's header was not used as it is poor and incomplete. Co-authored-by: Katayama Hirofumi MZ --- sdk/lib/CMakeLists.txt | 1 + sdk/lib/pathcch/CMakeLists.txt | 3 + sdk/lib/pathcch/pathcch.c | 1098 ++++++++++++++++++++++++++++++++ sdk/lib/pathcch/pathcch.h | 360 +++++++++++ 4 files changed, 1462 insertions(+) create mode 100644 sdk/lib/pathcch/CMakeLists.txt create mode 100644 sdk/lib/pathcch/pathcch.c create mode 100644 sdk/lib/pathcch/pathcch.h diff --git a/sdk/lib/CMakeLists.txt b/sdk/lib/CMakeLists.txt index ade5b4af3fb..61b051f6193 100644 --- a/sdk/lib/CMakeLists.txt +++ b/sdk/lib/CMakeLists.txt @@ -34,6 +34,7 @@ endif() add_subdirectory(ioevent) add_subdirectory(lsalib) add_subdirectory(nt) +add_subdirectory(pathcch) add_subdirectory(pseh) if(KDBG) diff --git a/sdk/lib/pathcch/CMakeLists.txt b/sdk/lib/pathcch/CMakeLists.txt new file mode 100644 index 00000000000..579dd177919 --- /dev/null +++ b/sdk/lib/pathcch/CMakeLists.txt @@ -0,0 +1,3 @@ + +add_library(pathcch pathcch.c) +add_dependencies(pathcch xdk) diff --git a/sdk/lib/pathcch/pathcch.c b/sdk/lib/pathcch/pathcch.c new file mode 100644 index 00000000000..9c690df3e68 --- /dev/null +++ b/sdk/lib/pathcch/pathcch.c @@ -0,0 +1,1098 @@ +/* + * Copyright 2018 Nikolay Sivov + * Copyright 2018 Zhiyi Zhang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +/* Wine code is still stuck in the past... */ +#ifdef __REACTOS__ +#define wcsnicmp _wcsnicmp +#endif + +#include +#include + +/* The PathCch functions use size_t, but Wine's implementation uses SIZE_T, + * so temporarily change the define'd SIZE_T type to the compatible one... */ +#ifdef __REACTOS__ +#undef SIZE_T +#define SIZE_T size_t +#endif + +/* This is the static implementation of the PathCch functions */ +#define STATIC_PATHCCH +#ifdef __GNUC__ // GCC doesn't support #pragma deprecated() +#undef DEPRECATE_SUPPORTED +#endif +#include + +#include + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(path); + +#ifdef __REACTOS__ +#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) || (DLL_EXPORT_VERSION < _WIN32_WINNT_VISTA) +/* wcsnlen is an NT6+ function. To cover all cases, use a private implementation */ +static inline size_t hacked_wcsnlen(const wchar_t* str, size_t size) +{ + StringCchLengthW(str, size, &size); + return size; +} +#define wcsnlen hacked_wcsnlen +#endif +#endif /* __REACTOS__ */ + +static BOOL is_drive_spec( const WCHAR *str ) +{ + return isalpha( str[0] ) && str[1] == ':'; +} + +#if 0 +static BOOL is_escaped_drive_spec( const WCHAR *str ) +{ + return isalpha( str[0] ) && (str[1] == ':' || str[1] == '|'); +} +#endif + +static BOOL is_prefixed_unc(const WCHAR *string) +{ + return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 ); +} + +static BOOL is_prefixed_disk(const WCHAR *string) +{ + return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 ); +} + +static BOOL is_prefixed_volume(const WCHAR *string) +{ + const WCHAR *guid; + INT i = 0; + + if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE; + + guid = string + 10; + + while (i <= 37) + { + switch (i) + { + case 0: + if (guid[i] != '{') return FALSE; + break; + case 9: + case 14: + case 19: + case 24: + if (guid[i] != '-') return FALSE; + break; + case 37: + if (guid[i] != '}') return FALSE; + break; + default: + if (!isxdigit(guid[i])) return FALSE; + break; + } + i++; + } + + return TRUE; +} + +/* Get the next character beyond end of the segment. + Return TRUE if the last segment ends with a backslash */ +static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment) +{ + while (*next && *next != '\\') next++; + if (*next == '\\') + { + *next_segment = next + 1; + return TRUE; + } + else + { + *next_segment = next; + return FALSE; + } +} + +/* Find the last character of the root in a path, if there is one, without any segments */ +static const WCHAR *get_root_end(const WCHAR *path) +{ + /* Find path root */ + if (is_prefixed_volume(path)) + return path[48] == '\\' ? path + 48 : path + 47; + else if (is_prefixed_unc(path)) + return path + 7; + else if (is_prefixed_disk(path)) + return path[6] == '\\' ? path + 6 : path + 5; + /* \\ */ + else if (path[0] == '\\' && path[1] == '\\') + return path + 1; + /* \ */ + else if (path[0] == '\\') + return path; + /* X:\ */ + else if (is_drive_spec( path )) + return path[2] == '\\' ? path + 2 : path + 1; + else + return NULL; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathAllocCanonicalize( + _In_ PCWSTR path_in, + _In_ /* PATHCCH_OPTIONS */ ULONG flags, + _Outptr_ PWSTR* path_out) +#else +HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out) +#endif +{ + WCHAR *buffer, *dst; + const WCHAR *src; + const WCHAR *root_end; + SIZE_T buffer_size, length; + + TRACE("%s %#lx %p\n", debugstr_w(path_in), flags, path_out); + + if (!path_in || !path_out + || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)) + || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS) + && !(flags & PATHCCH_ALLOW_LONG_PATHS)) + || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS))) + { + if (path_out) *path_out = NULL; + return E_INVALIDARG; + } + + length = lstrlenW(path_in); + if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))) + || (length + 1 > PATHCCH_MAX_CCH)) + { + *path_out = NULL; + return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); + } + + /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */ + if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS; + + /* path length + possible \\?\ addition + possible \ addition + NUL */ + buffer_size = (length + 6) * sizeof(WCHAR); + buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size); + if (!buffer) + { + *path_out = NULL; + return E_OUTOFMEMORY; + } + + src = path_in; + dst = buffer; + + root_end = get_root_end(path_in); + if (root_end) root_end = buffer + (root_end - path_in); + + /* Copy path root */ + if (root_end) + { + memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR)); + src += root_end - buffer + 1; + if(PathCchStripPrefix(dst, length + 6) == S_OK) + { + /* Fill in \ in X:\ if the \ is missing */ + if (is_drive_spec( dst ) && dst[2]!= '\\') + { + dst[2] = '\\'; + dst[3] = 0; + } + dst = buffer + lstrlenW(buffer); + root_end = dst; + } + else + dst += root_end - buffer + 1; + } + + while (*src) + { + if (src[0] == '.') + { + if (src[1] == '.') + { + /* Keep one . after * */ + if (dst > buffer && dst[-1] == '*') + { + *dst++ = *src++; + continue; + } + + /* Keep the .. if not surrounded by \ */ + if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\')) + { + *dst++ = *src++; + *dst++ = *src++; + continue; + } + + /* Remove the \ before .. if the \ is not part of root */ + if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) + { + *--dst = '\0'; + /* Remove characters until a \ is encountered */ + while (dst > buffer) + { + if (dst[-1] == '\\') + { + *--dst = 0; + break; + } + else + *--dst = 0; + } + } + /* Remove the extra \ after .. if the \ before .. wasn't deleted */ + else if (src[2] == '\\') + src++; + + src += 2; + } + else + { + /* Keep the . if not surrounded by \ */ + if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\')) + { + *dst++ = *src++; + continue; + } + + /* Remove the \ before . if the \ is not part of root */ + if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--; + /* Remove the extra \ after . if the \ before . wasn't deleted */ + else if (src[1] == '\\') + src++; + + src++; + } + + /* If X:\ is not complete, then complete it */ + if (is_drive_spec( buffer ) && buffer[2] != '\\') + { + root_end = buffer + 2; + dst = buffer + 3; + buffer[2] = '\\'; + /* If next character is \, use the \ to fill in */ + if (src[0] == '\\') src++; + } + } + /* Copy over */ + else + *dst++ = *src++; + } + /* End the path */ + *dst = 0; + + /* Strip multiple trailing . */ + if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS)) + { + while (dst > buffer && dst[-1] == '.') + { + /* Keep a . after * */ + if (dst - 1 > buffer && dst[-2] == '*') + break; + /* If . follow a : at the second character, remove the . and add a \ */ + else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1) + *--dst = '\\'; + else + *--dst = 0; + } + } + + /* If result path is empty, fill in \ */ + if (!*buffer) + { + buffer[0] = '\\'; + buffer[1] = 0; + } + + /* Extend the path if needed */ + length = lstrlenW(buffer); + if (((length + 1 > MAX_PATH && is_drive_spec( buffer )) + || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)) + && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS)) + { + memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR)); + buffer[0] = '\\'; + buffer[1] = '\\'; + buffer[2] = '?'; + buffer[3] = '\\'; + } + + /* Add a trailing backslash to the path if needed */ + if (flags & PATHCCH_ENSURE_TRAILING_SLASH) + PathCchAddBackslash(buffer, buffer_size); + + *path_out = buffer; + return S_OK; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathAllocCombine( + _In_opt_ PCWSTR path1, + _In_opt_ PCWSTR path2, + _In_ /* PATHCCH_OPTIONS */ ULONG flags, + _Outptr_ PWSTR* out) +#else +HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out) +#endif +{ + SIZE_T combined_length, length2; + WCHAR *combined_path; + BOOL add_backslash = FALSE; + HRESULT hr; + + TRACE("%s %s %#lx %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out); + + if ((!path1 && !path2) || !out) + { + if (out) *out = NULL; + return E_INVALIDARG; + } + + if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out); + + /* If path2 is fully qualified, use path2 only */ + if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\')) + { + path1 = path2; + path2 = NULL; + add_backslash = (is_drive_spec(path1) && !path1[2]) + || (is_prefixed_disk(path1) && !path1[6]); + } + + length2 = path2 ? lstrlenW(path2) : 0; + /* path1 length + path2 length + possible backslash + NULL */ + combined_length = lstrlenW(path1) + length2 + 2; + + combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR)); + if (!combined_path) + { + *out = NULL; + return E_OUTOFMEMORY; + } + + lstrcpyW(combined_path, path1); + PathCchStripPrefix(combined_path, combined_length); + if (add_backslash) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL); + + if (path2 && path2[0]) + { + if (path2[0] == '\\' && path2[1] != '\\') + { + PathCchStripToRoot(combined_path, combined_length); + path2++; + } + + PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL); + lstrcatW(combined_path, path2); + } + + hr = PathAllocCanonicalize(combined_path, flags, out); + HeapFree(GetProcessHeap(), 0, combined_path); + return hr; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchAddBackslash( + _Inout_updates_(size) PWSTR path, + _In_ size_t size) +#else +HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size) +#endif +{ + return PathCchAddBackslashEx(path, size, NULL, NULL); +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchAddBackslashEx( + _Inout_updates_(size) PWSTR path, + _In_ size_t size, + _Outptr_opt_result_buffer_(*remaining) PWSTR* endptr, + _Out_opt_ size_t* remaining) +#else +HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining) +#endif +{ + BOOL needs_termination; + SIZE_T length; + + TRACE("%s, %Iu, %p, %p\n", debugstr_w(path), size, endptr, remaining); + + length = lstrlenW(path); + needs_termination = size && length && path[length - 1] != '\\'; + + if (length >= (needs_termination ? size - 1 : size)) + { + if (endptr) *endptr = NULL; + if (remaining) *remaining = 0; + return STRSAFE_E_INSUFFICIENT_BUFFER; + } + + if (!needs_termination) + { + if (endptr) *endptr = path + length; + if (remaining) *remaining = size - length; + return S_FALSE; + } + + path[length++] = '\\'; + path[length] = 0; + + if (endptr) *endptr = path + length; + if (remaining) *remaining = size - length; + + return S_OK; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchAddExtension( + _Inout_updates_(size) PWSTR path, + _In_ size_t size, + _In_ PCWSTR extension) +#else +HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension) +#endif +{ + const WCHAR *existing_extension, *next; + SIZE_T path_length, extension_length, dot_length; + BOOL has_dot; + HRESULT hr; + + TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension)); + + if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG; + + next = extension; + while (*next) + { + if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG; + next++; + } + + has_dot = extension[0] == '.'; + + hr = PathCchFindExtension(path, size, &existing_extension); + if (FAILED(hr)) return hr; + if (*existing_extension) return S_FALSE; + + path_length = wcsnlen(path, size); + dot_length = has_dot ? 0 : 1; + extension_length = lstrlenW(extension); + + if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER; + + /* If extension is empty or only dot, return S_OK with path unchanged */ + if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK; + + if (!has_dot) + { + path[path_length] = '.'; + path_length++; + } + + lstrcpyW(path + path_length, extension); + return S_OK; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchAppend( + _Inout_updates_(size) PWSTR path1, + _In_ size_t size, + _In_opt_ PCWSTR path2) +#else +HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2) +#endif +{ + TRACE("%s %Iu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2)); + + return PathCchAppendEx(path1, size, path2, PATHCCH_NONE); +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchAppendEx( + _Inout_updates_(size) PWSTR path1, + _In_ size_t size, + _In_opt_ PCWSTR path2, + _In_ /* PATHCCH_OPTIONS */ ULONG flags) +#else +HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags) +#endif +{ + HRESULT hr; + WCHAR *result; + + TRACE("%s %Iu %s %#lx\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags); + + if (!path1 || !size) return E_INVALIDARG; + + /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs. + * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output + * buffer for PathCchCombineEx */ + result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)); + if (!result) return E_OUTOFMEMORY; + + /* Avoid the single backslash behavior with PathCchCombineEx when appending */ + if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++; + + hr = PathCchCombineEx(result, size, path1, path2, flags); + if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR)); + + HeapFree(GetProcessHeap(), 0, result); + return hr; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchCanonicalize( + _Out_writes_(size) PWSTR out, + _In_ size_t size, + _In_ PCWSTR in) +#else +HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in) +#endif +{ + TRACE("%p %Iu %s\n", out, size, wine_dbgstr_w(in)); + + /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */ + if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\')) + return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); + + return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE); +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchCanonicalizeEx( + _Out_writes_(size) PWSTR out, + _In_ size_t size, + _In_ PCWSTR in, + _In_ /* PATHCCH_OPTIONS */ ULONG flags) +#else +HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags) +#endif +{ + WCHAR *buffer; + SIZE_T length; + HRESULT hr; + + TRACE("%p %Iu %s %#lx\n", out, size, wine_dbgstr_w(in), flags); + + if (!size) return E_INVALIDARG; + + hr = PathAllocCanonicalize(in, flags, &buffer); + if (FAILED(hr)) return hr; + + length = lstrlenW(buffer); + if (size < length + 1) + { + /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */ + if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\'))) + hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); + else + hr = STRSAFE_E_INSUFFICIENT_BUFFER; + } + + if (SUCCEEDED(hr)) + { + memcpy(out, buffer, (length + 1) * sizeof(WCHAR)); + + /* Fill a backslash at the end of X: */ + if (is_drive_spec( out ) && !out[2] && size > 3) + { + out[2] = '\\'; + out[3] = 0; + } + } + + LocalFree(buffer); + return hr; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchCombine( + _Out_writes_(size) PWSTR out, + _In_ size_t size, + _In_opt_ PCWSTR path1, + _In_opt_ PCWSTR path2) +#else +HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2) +#endif +{ + TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2)); + + return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE); +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchCombineEx( + _Out_writes_(size) PWSTR out, + _In_ size_t size, + _In_opt_ PCWSTR path1, + _In_opt_ PCWSTR path2, + _In_ /* PATHCCH_OPTIONS */ ULONG flags) +#else +HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags) +#endif +{ + HRESULT hr; + WCHAR *buffer; + SIZE_T length; + + TRACE("%p %s %s %#lx\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags); + + if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; + + hr = PathAllocCombine(path1, path2, flags, &buffer); + if (FAILED(hr)) + { + out[0] = 0; + return hr; + } + + length = lstrlenW(buffer); + if (length + 1 > size) + { + out[0] = 0; + LocalFree(buffer); + return STRSAFE_E_INSUFFICIENT_BUFFER; + } + else + { + memcpy(out, buffer, (length + 1) * sizeof(WCHAR)); + LocalFree(buffer); + return S_OK; + } +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchFindExtension( + _In_reads_(size) PCWSTR path, + _In_ size_t size, + _Outptr_ PCWSTR* extension) +#else +HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension) +#endif +{ + const WCHAR *lastpoint = NULL; + SIZE_T counter = 0; + + TRACE("%s %Iu %p\n", wine_dbgstr_w(path), size, extension); + + if (!path || !size || size > PATHCCH_MAX_CCH) + { + *extension = NULL; + return E_INVALIDARG; + } + + while (*path) + { + if (*path == '\\' || *path == ' ') + lastpoint = NULL; + else if (*path == '.') + lastpoint = path; + + path++; + counter++; + if (counter == size || counter == PATHCCH_MAX_CCH) + { + *extension = NULL; + return E_INVALIDARG; + } + } + + *extension = lastpoint ? lastpoint : path; + return S_OK; +} + +#ifdef __REACTOS__ +BOOL +APIENTRY +PathCchIsRoot( + _In_opt_ PCWSTR path) +#else +BOOL WINAPI PathCchIsRoot(const WCHAR *path) +#endif +{ + const WCHAR *root_end; + const WCHAR *next; + BOOL is_unc; + + TRACE("%s\n", wine_dbgstr_w(path)); + + if (!path || !*path) return FALSE; + + root_end = get_root_end(path); + if (!root_end) return FALSE; + + if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')) + { + next = root_end + 1; + /* No extra segments */ + if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE; + + /* Has first segment with an ending backslash but no remaining characters */ + if (get_next_segment(next, &next) && !*next) return FALSE; + /* Has first segment with no ending backslash */ + else if (!*next) + return TRUE; + /* Has first segment with an ending backslash and has remaining characters*/ + else + { + next++; + /* Second segment must have no backslash and no remaining characters */ + return !get_next_segment(next, &next) && !*next; + } + } + else if (*root_end == '\\' && !root_end[1]) + return TRUE; + else + return FALSE; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchRemoveBackslash( + _Inout_updates_(path_size) PWSTR path, + _In_ size_t path_size) +#else +HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size) +#endif +{ + WCHAR *path_end; + SIZE_T free_size; + + TRACE("%s %Iu\n", debugstr_w(path), path_size); + + return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size); +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchRemoveBackslashEx( + _Inout_updates_(path_size) PWSTR path, + _In_ size_t path_size, + _Outptr_opt_result_buffer_(*free_size) PWSTR* path_end, + _Out_opt_ size_t* free_size) +#else +HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size) +#endif +{ + const WCHAR *root_end; + SIZE_T path_length; + + TRACE("%s %Iu %p %p\n", debugstr_w(path), path_size, path_end, free_size); + + if (!path_size || !path_end || !free_size) + { + if (path_end) *path_end = NULL; + if (free_size) *free_size = 0; + return E_INVALIDARG; + } + + path_length = wcsnlen(path, path_size); + if (path_length == path_size && !path[path_length]) return E_INVALIDARG; + + root_end = get_root_end(path); + if (path_length > 0 && path[path_length - 1] == '\\') + { + *path_end = path + path_length - 1; + *free_size = path_size - path_length + 1; + /* If the last character is beyond end of root */ + if (!root_end || path + path_length - 1 > root_end) + { + path[path_length - 1] = 0; + return S_OK; + } + else + return S_FALSE; + } + else + { + *path_end = path + path_length; + *free_size = path_size - path_length; + return S_FALSE; + } +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchRemoveExtension( + _Inout_updates_(size) PWSTR path, + _In_ size_t size) +#else +HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size) +#endif +{ + const WCHAR *extension; + WCHAR *next; + HRESULT hr; + + TRACE("%s %Iu\n", wine_dbgstr_w(path), size); + + if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; + + hr = PathCchFindExtension(path, size, &extension); + if (FAILED(hr)) return hr; + + next = path + (extension - path); + while (next - path < size && *next) *next++ = 0; + + return next == extension ? S_FALSE : S_OK; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchRemoveFileSpec( + _Inout_updates_(size) PWSTR path, + _In_ size_t size) +#else +HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size) +#endif +{ + const WCHAR *root_end = NULL; + SIZE_T length; + WCHAR *last; + + TRACE("%s %Iu\n", wine_dbgstr_w(path), size); + + if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; + + if (PathCchIsRoot(path)) return S_FALSE; + + PathCchSkipRoot(path, &root_end); + + /* The backslash at the end of UNC and \\* are not considered part of root in this case */ + if (root_end && root_end > path && root_end[-1] == '\\' + && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))) + root_end--; + + length = lstrlenW(path); + last = path + length - 1; + while (last >= path && (!root_end || last >= root_end)) + { + if (last - path >= size) return E_INVALIDARG; + + if (*last == '\\') + { + *last-- = 0; + break; + } + + *last-- = 0; + } + + return last != path + length - 1 ? S_OK : S_FALSE; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchRenameExtension( + _Inout_updates_(size) PWSTR path, + _In_ size_t size, + _In_ PCWSTR extension) +#else +HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension) +#endif +{ + HRESULT hr; + + TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension)); + + hr = PathCchRemoveExtension(path, size); + if (FAILED(hr)) return hr; + + hr = PathCchAddExtension(path, size, extension); + return FAILED(hr) ? hr : S_OK; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchSkipRoot( + _In_ PCWSTR path, + _Outptr_ PCWSTR* root_end) +#else +HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end) +#endif +{ + TRACE("%s %p\n", debugstr_w(path), root_end); + + if (!path || !path[0] || !root_end + || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path) + && !is_prefixed_disk(path))) + return E_INVALIDARG; + + *root_end = get_root_end(path); + if (*root_end) + { + (*root_end)++; + if (is_prefixed_unc(path)) + { + get_next_segment(*root_end, root_end); + get_next_segment(*root_end, root_end); + } + else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?') + { + /* Skip share server */ + get_next_segment(*root_end, root_end); + /* If mount point is empty, don't skip over mount point */ + if (**root_end != '\\') get_next_segment(*root_end, root_end); + } + } + + return *root_end ? S_OK : E_INVALIDARG; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchStripPrefix( + _Inout_updates_(size) PWSTR path, + _In_ size_t size) +#else +HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size) +#endif +{ + TRACE("%s %Iu\n", wine_dbgstr_w(path), size); + + if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; + + if (is_prefixed_unc(path)) + { + /* \\?\UNC\a -> \\a */ + if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG; + lstrcpyW(path + 2, path + 8); + return S_OK; + } + else if (is_prefixed_disk(path)) + { + /* \\?\C:\ -> C:\ */ + if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG; + lstrcpyW(path, path + 4); + return S_OK; + } + else + return S_FALSE; +} + +#ifdef __REACTOS__ +HRESULT +APIENTRY +PathCchStripToRoot( + _Inout_updates_(size) PWSTR path, + _In_ size_t size) +#else +HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size) +#endif +{ + const WCHAR *root_end; + WCHAR *segment_end; + BOOL is_unc; + + TRACE("%s %Iu\n", wine_dbgstr_w(path), size); + + if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG; + + /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped, + * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b + * \\\\a\\b\\c -> \\\\a\\b */ + if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')) + { + root_end = is_unc ? path + 8 : path + 3; + if (!get_next_segment(root_end, &root_end)) return S_FALSE; + if (!get_next_segment(root_end, &root_end)) return S_FALSE; + + if (root_end - path >= size) return E_INVALIDARG; + + segment_end = path + (root_end - path) - 1; + *segment_end = 0; + return S_OK; + } + else if (PathCchSkipRoot(path, &root_end) == S_OK) + { + if (root_end - path >= size) return E_INVALIDARG; + + segment_end = path + (root_end - path); + if (!*segment_end) return S_FALSE; + + *segment_end = 0; + return S_OK; + } + else + return E_INVALIDARG; +} + +#ifdef __REACTOS__ +BOOL +APIENTRY +PathIsUNCEx( + _In_ PCWSTR path, + _Outptr_opt_ PCWSTR* server) +#else +BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server) +#endif +{ + const WCHAR *result = NULL; + + TRACE("%s %p\n", wine_dbgstr_w(path), server); + + if (is_prefixed_unc(path)) + result = path + 8; + else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?') + result = path + 2; + + if (server) *server = result; + return !!result; +} diff --git a/sdk/lib/pathcch/pathcch.h b/sdk/lib/pathcch/pathcch.h new file mode 100644 index 00000000000..ddda4f1a90a --- /dev/null +++ b/sdk/lib/pathcch/pathcch.h @@ -0,0 +1,360 @@ +/* + * PROJECT: ReactOS PSDK + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: "Secure" shell path manipulation functions + * COPYRIGHT: MinGW-64 and Microsoft Corporation. + */ + +/** + * This file is part of the mingw-w64 runtime package. + * No warranty is given; refer to the file DISCLAIMER within this package. + */ + +#pragma once + + +#ifndef WINBASEAPI +#ifndef _KERNEL32_ +#define WINBASEAPI DECLSPEC_IMPORT +#else +#define WINBASEAPI +#endif +#endif + + +#ifndef WINPATHCCHAPI +#ifndef STATIC_PATHCCH +#define WINPATHCCHAPI WINBASEAPI +#else +#define WINPATHCCHAPI +#endif +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +// typedef enum PATHCCH_OPTIONS +#define PATHCCH_NONE 0x00 +#define PATHCCH_ALLOW_LONG_PATHS 0x01 +#define PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS 0x02 +#define PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS 0x04 +#define PATHCCH_DO_NOT_NORMALIZE_SEGMENTS 0x08 +#define PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH 0x10 +#define PATHCCH_ENSURE_TRAILING_SLASH 0x20 +// DEFINE_ENUM_FLAG_OPERATORS(PATHCCH_OPTIONS) + +#define VOLUME_PREFIX L"\\\\?\\Volume" +#define VOLUME_PREFIX_LEN (ARRAYSIZE(VOLUME_PREFIX) - 1) + +#define PATHCCH_MAX_CCH 0x8000 + +WINPATHCCHAPI +HRESULT +APIENTRY +PathAllocCanonicalize( + _In_ PCWSTR pszPathIn, + _In_ /* PATHCCH_OPTIONS */ ULONG dwFlags, + _Outptr_ PWSTR* ppszPathOut); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathAllocCombine( + _In_opt_ PCWSTR pszPathIn, + _In_opt_ PCWSTR pszMore, + _In_ /* PATHCCH_OPTIONS */ ULONG dwFlags, + _Outptr_ PWSTR* ppszPathOut); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchAddBackslash( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchAddBackslashEx( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _Outptr_opt_result_buffer_(*pcchRemaining) PWSTR* ppszEnd, + _Out_opt_ size_t* pcchRemaining); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchAddExtension( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _In_ PCWSTR pszExt); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchAppend( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _In_opt_ PCWSTR pszMore); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchAppendEx( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _In_opt_ PCWSTR pszMore, + _In_ /* PATHCCH_OPTIONS */ ULONG dwFlags); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchCanonicalize( + _Out_writes_(cchPathOut) PWSTR pszPathOut, + _In_ size_t cchPathOut, + _In_ PCWSTR pszPathIn); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchCanonicalizeEx( + _Out_writes_(cchPathOut) PWSTR pszPathOut, + _In_ size_t cchPathOut, + _In_ PCWSTR pszPathIn, + _In_ /* PATHCCH_OPTIONS */ ULONG dwFlags); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchCombine( + _Out_writes_(cchPathOut) PWSTR pszPathOut, + _In_ size_t cchPathOut, + _In_opt_ PCWSTR pszPathIn, + _In_opt_ PCWSTR pszMore); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchCombineEx( + _Out_writes_(cchPathOut) PWSTR pszPathOut, + _In_ size_t cchPathOut, + _In_opt_ PCWSTR pszPathIn, + _In_opt_ PCWSTR pszMore, + _In_ /* PATHCCH_OPTIONS */ ULONG dwFlags); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchFindExtension( + _In_reads_(cchPath) PCWSTR pszPath, + _In_ size_t cchPath, + _Outptr_ PCWSTR* ppszExt); + +WINPATHCCHAPI +BOOL +APIENTRY +PathCchIsRoot( + _In_opt_ PCWSTR pszPath); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchRemoveBackslash( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchRemoveBackslashEx( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _Outptr_opt_result_buffer_(*pcchRemaining) PWSTR* ppszEnd, + _Out_opt_ size_t* pcchRemaining); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchRemoveExtension( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchRemoveFileSpec( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchRenameExtension( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _In_ PCWSTR pszExt); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchSkipRoot( + _In_ PCWSTR pszPath, + _Outptr_ PCWSTR* ppszRootEnd); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchStripPrefix( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath); + +WINPATHCCHAPI +HRESULT +APIENTRY +PathCchStripToRoot( + _Inout_updates_(cchPath) PWSTR pszPath, + _In_ size_t cchPath); + +WINPATHCCHAPI +BOOL +APIENTRY +PathIsUNCEx( + _In_ PCWSTR pszPath, + _Outptr_opt_ PCWSTR* ppszServer); + + +#ifndef PATHCCH_NO_DEPRECATE + +#undef PathAddBackslash +#undef PathAddBackslashA +#undef PathAddBackslashW + +#undef PathAddExtension +#undef PathAddExtensionA +#undef PathAddExtensionW + +#undef PathAppend +#undef PathAppendA +#undef PathAppendW + +#undef PathCanonicalize +#undef PathCanonicalizeA +#undef PathCanonicalizeW + +#undef PathCombine +#undef PathCombineA +#undef PathCombineW + +#undef PathRenameExtension +#undef PathRenameExtensionA +#undef PathRenameExtensionW + + +#ifdef DEPRECATE_SUPPORTED + +// #pragma deprecated(PathIsRelativeWorker) +// #pragma deprecated(StrIsEqualWorker) +// #pragma deprecated(FindPreviousBackslashWorker) +// #pragma deprecated(IsHexDigitWorker) +// #pragma deprecated(StringIsGUIDWorker) +// #pragma deprecated(PathIsVolumeGUIDWorker) +// #pragma deprecated(IsValidExtensionWorker) + +#pragma deprecated(PathAddBackslash) +#pragma deprecated(PathAddBackslashA) +#pragma deprecated(PathAddBackslashW) + +#pragma deprecated(PathAddExtension) +#pragma deprecated(PathAddExtensionA) +#pragma deprecated(PathAddExtensionW) + +#pragma deprecated(PathAppend) +#pragma deprecated(PathAppendA) +#pragma deprecated(PathAppendW) + +#pragma deprecated(PathCanonicalize) +#pragma deprecated(PathCanonicalizeA) +#pragma deprecated(PathCanonicalizeW) + +#pragma deprecated(PathCombine) +#pragma deprecated(PathCombineA) +#pragma deprecated(PathCombineW) + +#pragma deprecated(PathRenameExtension) +#pragma deprecated(PathRenameExtensionA) +#pragma deprecated(PathRenameExtensionW) + +#else // !DEPRECATE_SUPPORTED + +// #define PathIsRelativeWorker PathIsRelativeWorker_is_internal_to_pathcch; +// #define StrIsEqualWorker StrIsEqualWorker_is_internal_to_pathcch; +// #define FindPreviousBackslashWorker FindPreviousBackslashWorker_is_internal_to_pathcch; +// #define IsHexDigitWorker IsHexDigitWorker_is_internal_to_pathcch; +// #define StringIsGUIDWorker StringIsGUIDWorker_is_internal_to_pathcch; +// #define PathIsVolumeGUIDWorker PathIsVolumeGUIDWorker_is_internal_to_pathcch; +// #define IsValidExtensionWorker IsValidExtensionWorker_is_internal_to_pathcch; + +#define PathAddBackslash PathAddBackslash_instead_use_PathCchAddBackslash; +#define PathAddBackslashA PathAddBackslash_instead_use_PathCchAddBackslash; +#define PathAddBackslashW PathAddBackslash_instead_use_PathCchAddBackslash; + +#define PathAddExtension PathAddExtension_instead_use_PathCchAddExtension; +#define PathAddExtensionA PathAddExtension_instead_use_PathCchAddExtension; +#define PathAddExtensionW PathAddExtension_instead_use_PathCchAddExtension; + +#define PathAppend PathAppend_instead_use_PathCchAppend; +#define PathAppendA PathAppend_instead_use_PathCchAppend; +#define PathAppendW PathAppend_instead_use_PathCchAppend; + +#define PathCanonicalize PathCanonicalize_instead_use_PathCchCanonicalize; +#define PathCanonicalizeA PathCanonicalize_instead_use_PathCchCanonicalize; +#define PathCanonicalizeW PathCanonicalize_instead_use_PathCchCanonicalize; + +#define PathCombine PathCombine_instead_use_PathCchCombine; +#define PathCombineA PathCombine_instead_use_PathCchCombine; +#define PathCombineW PathCombine_instead_use_PathCchCombine; + +#define PathRenameExtension PathRenameExtension_instead_use_PathCchRenameExtension; +#define PathRenameExtensionA PathRenameExtension_instead_use_PathCchRenameExtension; +#define PathRenameExtensionW PathRenameExtension_instead_use_PathCchRenameExtension; + +#endif // DEPRECATE_SUPPORTED + +#endif // PATHCCH_NO_DEPRECATE + +#ifdef __cplusplus +} +#endif + + +/* C++ non-const overloads */ +#ifdef __cplusplus + +__inline HRESULT +PathCchFindExtension( + _In_reads_(cchPath) PWSTR pszPath, + _In_ size_t cchPath, + _Outptr_ PWSTR* ppszExt) +{ + return PathCchFindExtension(const_cast(pszPath), cchPath, const_cast(ppszExt)); +} + +__inline HRESULT +PathCchSkipRoot( + _In_ PWSTR pszPath, + _Outptr_ PWSTR* ppszRootEnd) +{ + return PathCchSkipRoot(const_cast(pszPath), const_cast(ppszRootEnd)); +} + +__inline BOOL +PathIsUNCEx( + _In_ PWSTR pszPath, + _Outptr_opt_ PWSTR* ppszServer) +{ + return PathIsUNCEx(const_cast(pszPath), const_cast(ppszServer)); +} + +#endif // __cplusplus