xref: /reactos/sdk/lib/pathcch/pathcch.c (revision 14d3b53c)
1 /*
2  * Copyright 2018 Nikolay Sivov
3  * Copyright 2018 Zhiyi Zhang
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include <stdarg.h>
21 #include <string.h>
22 
23 /* Wine code is still stuck in the past... */
24 #ifdef __REACTOS__
25 #define wcsnicmp _wcsnicmp
26 #endif
27 
28 #include <windef.h>
29 #include <winbase.h>
30 
31 /* The PathCch functions use size_t, but Wine's implementation uses SIZE_T,
32  * so temporarily change the define'd SIZE_T type to the compatible one... */
33 #ifdef __REACTOS__
34 #undef SIZE_T
35 #define SIZE_T size_t
36 #endif
37 
38 /* This is the static implementation of the PathCch functions */
39 #define STATIC_PATHCCH
40 #ifdef __GNUC__ // GCC doesn't support #pragma deprecated()
41 #undef DEPRECATE_SUPPORTED
42 #endif
43 #include <pathcch.h>
44 
45 #include <strsafe.h>
46 
47 #include "wine/debug.h"
48 
49 WINE_DEFAULT_DEBUG_CHANNEL(path);
50 
51 #ifdef __REACTOS__
52 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) || (DLL_EXPORT_VERSION < _WIN32_WINNT_VISTA)
53 /* wcsnlen is an NT6+ function. To cover all cases, use a private implementation */
54 static inline size_t hacked_wcsnlen(const wchar_t* str, size_t size)
55 {
56     StringCchLengthW(str, size, &size);
57     return size;
58 }
59 #define wcsnlen hacked_wcsnlen
60 #endif
61 #endif /* __REACTOS__ */
62 
63 static BOOL is_drive_spec( const WCHAR *str )
64 {
65     return isalpha( str[0] ) && str[1] == ':';
66 }
67 
68 #if 0
69 static BOOL is_escaped_drive_spec( const WCHAR *str )
70 {
71     return isalpha( str[0] ) && (str[1] == ':' || str[1] == '|');
72 }
73 #endif
74 
75 static BOOL is_prefixed_unc(const WCHAR *string)
76 {
77     return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 );
78 }
79 
80 static BOOL is_prefixed_disk(const WCHAR *string)
81 {
82     return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 );
83 }
84 
85 static BOOL is_prefixed_volume(const WCHAR *string)
86 {
87     const WCHAR *guid;
88     INT i = 0;
89 
90     if (wcsnicmp( string, L"\\\\?\\Volume", 10 )) return FALSE;
91 
92     guid = string + 10;
93 
94     while (i <= 37)
95     {
96         switch (i)
97         {
98         case 0:
99             if (guid[i] != '{') return FALSE;
100             break;
101         case 9:
102         case 14:
103         case 19:
104         case 24:
105             if (guid[i] != '-') return FALSE;
106             break;
107         case 37:
108             if (guid[i] != '}') return FALSE;
109             break;
110         default:
111             if (!isxdigit(guid[i])) return FALSE;
112             break;
113         }
114         i++;
115     }
116 
117     return TRUE;
118 }
119 
120 /* Get the next character beyond end of the segment.
121    Return TRUE if the last segment ends with a backslash */
122 static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment)
123 {
124     while (*next && *next != '\\') next++;
125     if (*next == '\\')
126     {
127         *next_segment = next + 1;
128         return TRUE;
129     }
130     else
131     {
132         *next_segment = next;
133         return FALSE;
134     }
135 }
136 
137 /* Find the last character of the root in a path, if there is one, without any segments */
138 static const WCHAR *get_root_end(const WCHAR *path)
139 {
140     /* Find path root */
141     if (is_prefixed_volume(path))
142         return path[48] == '\\' ? path + 48 : path + 47;
143     else if (is_prefixed_unc(path))
144         return path + 7;
145     else if (is_prefixed_disk(path))
146         return path[6] == '\\' ? path + 6 : path + 5;
147     /* \\ */
148     else if (path[0] == '\\' && path[1] == '\\')
149         return path + 1;
150     /* \ */
151     else if (path[0] == '\\')
152         return path;
153     /* X:\ */
154     else if (is_drive_spec( path ))
155         return path[2] == '\\' ? path + 2 : path + 1;
156     else
157         return NULL;
158 }
159 
160 #ifdef __REACTOS__
161 HRESULT
162 APIENTRY
163 PathAllocCanonicalize(
164     _In_ PCWSTR path_in,
165     _In_ /* PATHCCH_OPTIONS */ ULONG flags,
166     _Outptr_ PWSTR* path_out)
167 #else
168 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out)
169 #endif
170 {
171     WCHAR *buffer, *dst;
172     const WCHAR *src;
173     const WCHAR *root_end;
174     SIZE_T buffer_size, length;
175 
176     TRACE("%s %#lx %p\n", debugstr_w(path_in), flags, path_out);
177 
178     if (!path_in || !path_out
179         || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS))
180         || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)
181             && !(flags & PATHCCH_ALLOW_LONG_PATHS))
182         || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS)))
183     {
184         if (path_out) *path_out = NULL;
185         return E_INVALIDARG;
186     }
187 
188     length = lstrlenW(path_in);
189     if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)))
190         || (length + 1 > PATHCCH_MAX_CCH))
191     {
192         *path_out = NULL;
193         return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
194     }
195 
196     /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
197     if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS;
198 
199     /* path length + possible \\?\ addition + possible \ addition + NUL */
200     buffer_size = (length + 6) * sizeof(WCHAR);
201     buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size);
202     if (!buffer)
203     {
204         *path_out = NULL;
205         return E_OUTOFMEMORY;
206     }
207 
208     src = path_in;
209     dst = buffer;
210 
211     root_end = get_root_end(path_in);
212     if (root_end) root_end = buffer + (root_end - path_in);
213 
214     /* Copy path root */
215     if (root_end)
216     {
217         memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR));
218         src += root_end - buffer + 1;
219         if(PathCchStripPrefix(dst, length + 6) == S_OK)
220         {
221             /* Fill in \ in X:\ if the \ is missing */
222             if (is_drive_spec( dst ) && dst[2]!= '\\')
223             {
224                 dst[2] = '\\';
225                 dst[3] = 0;
226             }
227             dst = buffer + lstrlenW(buffer);
228             root_end = dst;
229         }
230         else
231             dst += root_end - buffer + 1;
232     }
233 
234     while (*src)
235     {
236         if (src[0] == '.')
237         {
238             if (src[1] == '.')
239             {
240                 /* Keep one . after * */
241                 if (dst > buffer && dst[-1] == '*')
242                 {
243                     *dst++ = *src++;
244                     continue;
245                 }
246 
247                 /* Keep the .. if not surrounded by \ */
248                 if ((src[2] != '\\' && src[2]) || (dst > buffer && dst[-1] != '\\'))
249                 {
250                     *dst++ = *src++;
251                     *dst++ = *src++;
252                     continue;
253                 }
254 
255                 /* Remove the \ before .. if the \ is not part of root */
256                 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
257                 {
258                     *--dst = '\0';
259                     /* Remove characters until a \ is encountered */
260                     while (dst > buffer)
261                     {
262                         if (dst[-1] == '\\')
263                         {
264                             *--dst = 0;
265                             break;
266                         }
267                         else
268                             *--dst = 0;
269                     }
270                 }
271                 /* Remove the extra \ after .. if the \ before .. wasn't deleted */
272                 else if (src[2] == '\\')
273                     src++;
274 
275                 src += 2;
276             }
277             else
278             {
279                 /* Keep the . if not surrounded by \ */
280                 if ((src[1] != '\\' && src[1]) || (dst > buffer && dst[-1] != '\\'))
281                 {
282                     *dst++ = *src++;
283                     continue;
284                 }
285 
286                 /* Remove the \ before . if the \ is not part of root */
287                 if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--;
288                 /* Remove the extra \ after . if the \ before . wasn't deleted */
289                 else if (src[1] == '\\')
290                     src++;
291 
292                 src++;
293             }
294 
295             /* If X:\ is not complete, then complete it */
296             if (is_drive_spec( buffer ) && buffer[2] != '\\')
297             {
298                 root_end = buffer + 2;
299                 dst = buffer + 3;
300                 buffer[2] = '\\';
301                 /* If next character is \, use the \ to fill in */
302                 if (src[0] == '\\') src++;
303             }
304         }
305         /* Copy over */
306         else
307             *dst++ = *src++;
308     }
309     /* End the path */
310     *dst = 0;
311 
312     /* Strip multiple trailing . */
313     if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
314     {
315         while (dst > buffer && dst[-1] == '.')
316         {
317             /* Keep a . after * */
318             if (dst - 1 > buffer && dst[-2] == '*')
319                 break;
320             /* If . follow a : at the second character, remove the . and add a \ */
321             else if (dst - 1 > buffer && dst[-2] == ':' && dst - 2 == buffer + 1)
322                 *--dst = '\\';
323             else
324                 *--dst = 0;
325         }
326     }
327 
328     /* If result path is empty, fill in \ */
329     if (!*buffer)
330     {
331         buffer[0] = '\\';
332         buffer[1] = 0;
333     }
334 
335     /* Extend the path if needed */
336     length = lstrlenW(buffer);
337     if (((length + 1 > MAX_PATH && is_drive_spec( buffer ))
338          || (is_drive_spec( buffer ) && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))
339         && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS))
340     {
341         memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR));
342         buffer[0] = '\\';
343         buffer[1] = '\\';
344         buffer[2] = '?';
345         buffer[3] = '\\';
346     }
347 
348     /* Add a trailing backslash to the path if needed */
349     if (flags & PATHCCH_ENSURE_TRAILING_SLASH)
350         PathCchAddBackslash(buffer, buffer_size);
351 
352     *path_out = buffer;
353     return S_OK;
354 }
355 
356 #ifdef __REACTOS__
357 HRESULT
358 APIENTRY
359 PathAllocCombine(
360     _In_opt_ PCWSTR path1,
361     _In_opt_ PCWSTR path2,
362     _In_ /* PATHCCH_OPTIONS */ ULONG flags,
363     _Outptr_ PWSTR* out)
364 #else
365 HRESULT WINAPI PathAllocCombine(const WCHAR *path1, const WCHAR *path2, DWORD flags, WCHAR **out)
366 #endif
367 {
368     SIZE_T combined_length, length2;
369     WCHAR *combined_path;
370     BOOL add_backslash = FALSE;
371     HRESULT hr;
372 
373     TRACE("%s %s %#lx %p\n", wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags, out);
374 
375     if ((!path1 && !path2) || !out)
376     {
377         if (out) *out = NULL;
378         return E_INVALIDARG;
379     }
380 
381     if (!path1 || !path2) return PathAllocCanonicalize(path1 ? path1 : path2, flags, out);
382 
383     /* If path2 is fully qualified, use path2 only */
384     if (is_drive_spec( path2 ) || (path2[0] == '\\' && path2[1] == '\\'))
385     {
386         path1 = path2;
387         path2 = NULL;
388         add_backslash = (is_drive_spec(path1) && !path1[2])
389                         || (is_prefixed_disk(path1) && !path1[6]);
390     }
391 
392     length2 = path2 ? lstrlenW(path2) : 0;
393     /* path1 length + path2 length + possible backslash + NULL */
394     combined_length = lstrlenW(path1) + length2 + 2;
395 
396     combined_path = HeapAlloc(GetProcessHeap(), 0, combined_length * sizeof(WCHAR));
397     if (!combined_path)
398     {
399         *out = NULL;
400         return E_OUTOFMEMORY;
401     }
402 
403     lstrcpyW(combined_path, path1);
404     PathCchStripPrefix(combined_path, combined_length);
405     if (add_backslash) PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
406 
407     if (path2 && path2[0])
408     {
409         if (path2[0] == '\\' && path2[1] != '\\')
410         {
411             PathCchStripToRoot(combined_path, combined_length);
412             path2++;
413         }
414 
415         PathCchAddBackslashEx(combined_path, combined_length, NULL, NULL);
416         lstrcatW(combined_path, path2);
417     }
418 
419     hr = PathAllocCanonicalize(combined_path, flags, out);
420     HeapFree(GetProcessHeap(), 0, combined_path);
421     return hr;
422 }
423 
424 #ifdef __REACTOS__
425 HRESULT
426 APIENTRY
427 PathCchAddBackslash(
428     _Inout_updates_(size) PWSTR path,
429     _In_ size_t size)
430 #else
431 HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size)
432 #endif
433 {
434     return PathCchAddBackslashEx(path, size, NULL, NULL);
435 }
436 
437 #ifdef __REACTOS__
438 HRESULT
439 APIENTRY
440 PathCchAddBackslashEx(
441     _Inout_updates_(size) PWSTR path,
442     _In_ size_t size,
443     _Outptr_opt_result_buffer_(*remaining) PWSTR* endptr,
444     _Out_opt_ size_t* remaining)
445 #else
446 HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **endptr, SIZE_T *remaining)
447 #endif
448 {
449     BOOL needs_termination;
450     SIZE_T length;
451 
452     TRACE("%s, %Iu, %p, %p\n", debugstr_w(path), size, endptr, remaining);
453 
454     length = lstrlenW(path);
455     needs_termination = size && length && path[length - 1] != '\\';
456 
457     if (length >= (needs_termination ? size - 1 : size))
458     {
459         if (endptr) *endptr = NULL;
460         if (remaining) *remaining = 0;
461         return STRSAFE_E_INSUFFICIENT_BUFFER;
462     }
463 
464     if (!needs_termination)
465     {
466         if (endptr) *endptr = path + length;
467         if (remaining) *remaining = size - length;
468         return S_FALSE;
469     }
470 
471     path[length++] = '\\';
472     path[length] = 0;
473 
474     if (endptr) *endptr = path + length;
475     if (remaining) *remaining = size - length;
476 
477     return S_OK;
478 }
479 
480 #ifdef __REACTOS__
481 HRESULT
482 APIENTRY
483 PathCchAddExtension(
484     _Inout_updates_(size) PWSTR path,
485     _In_ size_t size,
486     _In_ PCWSTR extension)
487 #else
488 HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
489 #endif
490 {
491     const WCHAR *existing_extension, *next;
492     SIZE_T path_length, extension_length, dot_length;
493     BOOL has_dot;
494     HRESULT hr;
495 
496     TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
497 
498     if (!path || !size || size > PATHCCH_MAX_CCH || !extension) return E_INVALIDARG;
499 
500     next = extension;
501     while (*next)
502     {
503         if ((*next == '.' && next > extension) || *next == ' ' || *next == '\\') return E_INVALIDARG;
504         next++;
505     }
506 
507     has_dot = extension[0] == '.';
508 
509     hr = PathCchFindExtension(path, size, &existing_extension);
510     if (FAILED(hr)) return hr;
511     if (*existing_extension) return S_FALSE;
512 
513     path_length = wcsnlen(path, size);
514     dot_length = has_dot ? 0 : 1;
515     extension_length = lstrlenW(extension);
516 
517     if (path_length + dot_length + extension_length + 1 > size) return STRSAFE_E_INSUFFICIENT_BUFFER;
518 
519     /* If extension is empty or only dot, return S_OK with path unchanged */
520     if (!extension[0] || (extension[0] == '.' && !extension[1])) return S_OK;
521 
522     if (!has_dot)
523     {
524         path[path_length] = '.';
525         path_length++;
526     }
527 
528     lstrcpyW(path + path_length, extension);
529     return S_OK;
530 }
531 
532 #ifdef __REACTOS__
533 HRESULT
534 APIENTRY
535 PathCchAppend(
536     _Inout_updates_(size) PWSTR path1,
537     _In_ size_t size,
538     _In_opt_ PCWSTR path2)
539 #else
540 HRESULT WINAPI PathCchAppend(WCHAR *path1, SIZE_T size, const WCHAR *path2)
541 #endif
542 {
543     TRACE("%s %Iu %s\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2));
544 
545     return PathCchAppendEx(path1, size, path2, PATHCCH_NONE);
546 }
547 
548 #ifdef __REACTOS__
549 HRESULT
550 APIENTRY
551 PathCchAppendEx(
552     _Inout_updates_(size) PWSTR path1,
553     _In_ size_t size,
554     _In_opt_ PCWSTR path2,
555     _In_ /* PATHCCH_OPTIONS */ ULONG flags)
556 #else
557 HRESULT WINAPI PathCchAppendEx(WCHAR *path1, SIZE_T size, const WCHAR *path2, DWORD flags)
558 #endif
559 {
560     HRESULT hr;
561     WCHAR *result;
562 
563     TRACE("%s %Iu %s %#lx\n", wine_dbgstr_w(path1), size, wine_dbgstr_w(path2), flags);
564 
565     if (!path1 || !size) return E_INVALIDARG;
566 
567     /* Create a temporary buffer for result because we need to keep path1 unchanged if error occurs.
568      * And PathCchCombineEx writes empty result if there is error so we can't just use path1 as output
569      * buffer for PathCchCombineEx */
570     result = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
571     if (!result) return E_OUTOFMEMORY;
572 
573     /* Avoid the single backslash behavior with PathCchCombineEx when appending */
574     if (path2 && path2[0] == '\\' && path2[1] != '\\') path2++;
575 
576     hr = PathCchCombineEx(result, size, path1, path2, flags);
577     if (SUCCEEDED(hr)) memcpy(path1, result, size * sizeof(WCHAR));
578 
579     HeapFree(GetProcessHeap(), 0, result);
580     return hr;
581 }
582 
583 #ifdef __REACTOS__
584 HRESULT
585 APIENTRY
586 PathCchCanonicalize(
587     _Out_writes_(size) PWSTR out,
588     _In_ size_t size,
589     _In_ PCWSTR in)
590 #else
591 HRESULT WINAPI PathCchCanonicalize(WCHAR *out, SIZE_T size, const WCHAR *in)
592 #endif
593 {
594     TRACE("%p %Iu %s\n", out, size, wine_dbgstr_w(in));
595 
596     /* Not X:\ and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
597     if (lstrlenW(in) > MAX_PATH - 4 && !(is_drive_spec( in ) && in[2] == '\\'))
598         return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
599 
600     return PathCchCanonicalizeEx(out, size, in, PATHCCH_NONE);
601 }
602 
603 #ifdef __REACTOS__
604 HRESULT
605 APIENTRY
606 PathCchCanonicalizeEx(
607     _Out_writes_(size) PWSTR out,
608     _In_ size_t size,
609     _In_ PCWSTR in,
610     _In_ /* PATHCCH_OPTIONS */ ULONG flags)
611 #else
612 HRESULT WINAPI PathCchCanonicalizeEx(WCHAR *out, SIZE_T size, const WCHAR *in, DWORD flags)
613 #endif
614 {
615     WCHAR *buffer;
616     SIZE_T length;
617     HRESULT hr;
618 
619     TRACE("%p %Iu %s %#lx\n", out, size, wine_dbgstr_w(in), flags);
620 
621     if (!size) return E_INVALIDARG;
622 
623     hr = PathAllocCanonicalize(in, flags, &buffer);
624     if (FAILED(hr)) return hr;
625 
626     length = lstrlenW(buffer);
627     if (size < length + 1)
628     {
629         /* No root and path > MAX_PATH - 4, return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) */
630         if (length > MAX_PATH - 4 && !(in[0] == '\\' || (is_drive_spec( in ) && in[2] == '\\')))
631             hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
632         else
633             hr = STRSAFE_E_INSUFFICIENT_BUFFER;
634     }
635 
636     if (SUCCEEDED(hr))
637     {
638         memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
639 
640         /* Fill a backslash at the end of X: */
641         if (is_drive_spec( out ) && !out[2] && size > 3)
642         {
643             out[2] = '\\';
644             out[3] = 0;
645         }
646     }
647 
648     LocalFree(buffer);
649     return hr;
650 }
651 
652 #ifdef __REACTOS__
653 HRESULT
654 APIENTRY
655 PathCchCombine(
656     _Out_writes_(size) PWSTR out,
657     _In_ size_t size,
658     _In_opt_ PCWSTR path1,
659     _In_opt_ PCWSTR path2)
660 #else
661 HRESULT WINAPI PathCchCombine(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2)
662 #endif
663 {
664     TRACE("%p %s %s\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2));
665 
666     return PathCchCombineEx(out, size, path1, path2, PATHCCH_NONE);
667 }
668 
669 #ifdef __REACTOS__
670 HRESULT
671 APIENTRY
672 PathCchCombineEx(
673     _Out_writes_(size) PWSTR out,
674     _In_ size_t size,
675     _In_opt_ PCWSTR path1,
676     _In_opt_ PCWSTR path2,
677     _In_ /* PATHCCH_OPTIONS */ ULONG flags)
678 #else
679 HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, const WCHAR *path2, DWORD flags)
680 #endif
681 {
682     HRESULT hr;
683     WCHAR *buffer;
684     SIZE_T length;
685 
686     TRACE("%p %s %s %#lx\n", out, wine_dbgstr_w(path1), wine_dbgstr_w(path2), flags);
687 
688     if (!out || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
689 
690     hr = PathAllocCombine(path1, path2, flags, &buffer);
691     if (FAILED(hr))
692     {
693         out[0] = 0;
694         return hr;
695     }
696 
697     length = lstrlenW(buffer);
698     if (length + 1 > size)
699     {
700         out[0] = 0;
701         LocalFree(buffer);
702         return STRSAFE_E_INSUFFICIENT_BUFFER;
703     }
704     else
705     {
706         memcpy(out, buffer, (length + 1) * sizeof(WCHAR));
707         LocalFree(buffer);
708         return S_OK;
709     }
710 }
711 
712 #ifdef __REACTOS__
713 HRESULT
714 APIENTRY
715 PathCchFindExtension(
716     _In_reads_(size) PCWSTR path,
717     _In_ size_t size,
718     _Outptr_ PCWSTR* extension)
719 #else
720 HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension)
721 #endif
722 {
723     const WCHAR *lastpoint = NULL;
724     SIZE_T counter = 0;
725 
726     TRACE("%s %Iu %p\n", wine_dbgstr_w(path), size, extension);
727 
728     if (!path || !size || size > PATHCCH_MAX_CCH)
729     {
730         *extension = NULL;
731         return E_INVALIDARG;
732     }
733 
734     while (*path)
735     {
736         if (*path == '\\' || *path == ' ')
737             lastpoint = NULL;
738         else if (*path == '.')
739             lastpoint = path;
740 
741         path++;
742         counter++;
743         if (counter == size || counter == PATHCCH_MAX_CCH)
744         {
745             *extension = NULL;
746             return E_INVALIDARG;
747         }
748     }
749 
750     *extension = lastpoint ? lastpoint : path;
751     return S_OK;
752 }
753 
754 #ifdef __REACTOS__
755 BOOL
756 APIENTRY
757 PathCchIsRoot(
758     _In_opt_ PCWSTR path)
759 #else
760 BOOL WINAPI PathCchIsRoot(const WCHAR *path)
761 #endif
762 {
763     const WCHAR *root_end;
764     const WCHAR *next;
765     BOOL is_unc;
766 
767     TRACE("%s\n", wine_dbgstr_w(path));
768 
769     if (!path || !*path) return FALSE;
770 
771     root_end = get_root_end(path);
772     if (!root_end) return FALSE;
773 
774     if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
775     {
776         next = root_end + 1;
777         /* No extra segments */
778         if ((is_unc && !*next) || (!is_unc && !*next)) return TRUE;
779 
780         /* Has first segment with an ending backslash but no remaining characters */
781         if (get_next_segment(next, &next) && !*next) return FALSE;
782         /* Has first segment with no ending backslash */
783         else if (!*next)
784             return TRUE;
785         /* Has first segment with an ending backslash and has remaining characters*/
786         else
787         {
788             next++;
789             /* Second segment must have no backslash and no remaining characters */
790             return !get_next_segment(next, &next) && !*next;
791         }
792     }
793     else if (*root_end == '\\' && !root_end[1])
794         return TRUE;
795     else
796         return FALSE;
797 }
798 
799 #ifdef __REACTOS__
800 HRESULT
801 APIENTRY
802 PathCchRemoveBackslash(
803     _Inout_updates_(path_size) PWSTR path,
804     _In_ size_t path_size)
805 #else
806 HRESULT WINAPI PathCchRemoveBackslash(WCHAR *path, SIZE_T path_size)
807 #endif
808 {
809     WCHAR *path_end;
810     SIZE_T free_size;
811 
812     TRACE("%s %Iu\n", debugstr_w(path), path_size);
813 
814     return PathCchRemoveBackslashEx(path, path_size, &path_end, &free_size);
815 }
816 
817 #ifdef __REACTOS__
818 HRESULT
819 APIENTRY
820 PathCchRemoveBackslashEx(
821     _Inout_updates_(path_size) PWSTR path,
822     _In_ size_t path_size,
823     _Outptr_opt_result_buffer_(*free_size) PWSTR* path_end,
824     _Out_opt_ size_t* free_size)
825 #else
826 HRESULT WINAPI PathCchRemoveBackslashEx(WCHAR *path, SIZE_T path_size, WCHAR **path_end, SIZE_T *free_size)
827 #endif
828 {
829     const WCHAR *root_end;
830     SIZE_T path_length;
831 
832     TRACE("%s %Iu %p %p\n", debugstr_w(path), path_size, path_end, free_size);
833 
834     if (!path_size || !path_end || !free_size)
835     {
836         if (path_end) *path_end = NULL;
837         if (free_size) *free_size = 0;
838         return E_INVALIDARG;
839     }
840 
841     path_length = wcsnlen(path, path_size);
842     if (path_length == path_size && !path[path_length]) return E_INVALIDARG;
843 
844     root_end = get_root_end(path);
845     if (path_length > 0 && path[path_length - 1] == '\\')
846     {
847         *path_end = path + path_length - 1;
848         *free_size = path_size - path_length + 1;
849         /* If the last character is beyond end of root */
850         if (!root_end || path + path_length - 1 > root_end)
851         {
852             path[path_length - 1] = 0;
853             return S_OK;
854         }
855         else
856             return S_FALSE;
857     }
858     else
859     {
860         *path_end = path + path_length;
861         *free_size = path_size - path_length;
862         return S_FALSE;
863     }
864 }
865 
866 #ifdef __REACTOS__
867 HRESULT
868 APIENTRY
869 PathCchRemoveExtension(
870     _Inout_updates_(size) PWSTR path,
871     _In_ size_t size)
872 #else
873 HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size)
874 #endif
875 {
876     const WCHAR *extension;
877     WCHAR *next;
878     HRESULT hr;
879 
880     TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
881 
882     if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
883 
884     hr = PathCchFindExtension(path, size, &extension);
885     if (FAILED(hr)) return hr;
886 
887     next = path + (extension - path);
888     while (next - path < size && *next) *next++ = 0;
889 
890     return next == extension ? S_FALSE : S_OK;
891 }
892 
893 #ifdef __REACTOS__
894 HRESULT
895 APIENTRY
896 PathCchRemoveFileSpec(
897     _Inout_updates_(size) PWSTR path,
898     _In_ size_t size)
899 #else
900 HRESULT WINAPI PathCchRemoveFileSpec(WCHAR *path, SIZE_T size)
901 #endif
902 {
903     const WCHAR *root_end = NULL;
904     SIZE_T length;
905     WCHAR *last;
906 
907     TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
908 
909     if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
910 
911     if (PathCchIsRoot(path)) return S_FALSE;
912 
913     PathCchSkipRoot(path, &root_end);
914 
915     /* The backslash at the end of UNC and \\* are not considered part of root in this case */
916     if (root_end && root_end > path && root_end[-1] == '\\'
917         && (is_prefixed_unc(path) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?')))
918         root_end--;
919 
920     length = lstrlenW(path);
921     last = path + length - 1;
922     while (last >= path && (!root_end || last >= root_end))
923     {
924         if (last - path >= size) return E_INVALIDARG;
925 
926         if (*last == '\\')
927         {
928             *last-- = 0;
929             break;
930         }
931 
932         *last-- = 0;
933     }
934 
935     return last != path + length - 1 ? S_OK : S_FALSE;
936 }
937 
938 #ifdef __REACTOS__
939 HRESULT
940 APIENTRY
941 PathCchRenameExtension(
942     _Inout_updates_(size) PWSTR path,
943     _In_ size_t size,
944     _In_ PCWSTR extension)
945 #else
946 HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension)
947 #endif
948 {
949     HRESULT hr;
950 
951     TRACE("%s %Iu %s\n", wine_dbgstr_w(path), size, wine_dbgstr_w(extension));
952 
953     hr = PathCchRemoveExtension(path, size);
954     if (FAILED(hr)) return hr;
955 
956     hr = PathCchAddExtension(path, size, extension);
957     return FAILED(hr) ? hr : S_OK;
958 }
959 
960 #ifdef __REACTOS__
961 HRESULT
962 APIENTRY
963 PathCchSkipRoot(
964     _In_ PCWSTR path,
965     _Outptr_ PCWSTR* root_end)
966 #else
967 HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end)
968 #endif
969 {
970     TRACE("%s %p\n", debugstr_w(path), root_end);
971 
972     if (!path || !path[0] || !root_end
973         || (!wcsnicmp(path, L"\\\\?", 3) && !is_prefixed_volume(path) && !is_prefixed_unc(path)
974             && !is_prefixed_disk(path)))
975         return E_INVALIDARG;
976 
977     *root_end = get_root_end(path);
978     if (*root_end)
979     {
980         (*root_end)++;
981         if (is_prefixed_unc(path))
982         {
983             get_next_segment(*root_end, root_end);
984             get_next_segment(*root_end, root_end);
985         }
986         else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
987         {
988             /* Skip share server */
989             get_next_segment(*root_end, root_end);
990             /* If mount point is empty, don't skip over mount point */
991             if (**root_end != '\\') get_next_segment(*root_end, root_end);
992         }
993     }
994 
995     return *root_end ? S_OK : E_INVALIDARG;
996 }
997 
998 #ifdef __REACTOS__
999 HRESULT
1000 APIENTRY
1001 PathCchStripPrefix(
1002     _Inout_updates_(size) PWSTR path,
1003     _In_ size_t size)
1004 #else
1005 HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size)
1006 #endif
1007 {
1008     TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
1009 
1010     if (!path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
1011 
1012     if (is_prefixed_unc(path))
1013     {
1014         /* \\?\UNC\a -> \\a */
1015         if (size < lstrlenW(path + 8) + 3) return E_INVALIDARG;
1016         lstrcpyW(path + 2, path + 8);
1017         return S_OK;
1018     }
1019     else if (is_prefixed_disk(path))
1020     {
1021         /* \\?\C:\ -> C:\ */
1022         if (size < lstrlenW(path + 4) + 1) return E_INVALIDARG;
1023         lstrcpyW(path, path + 4);
1024         return S_OK;
1025     }
1026     else
1027         return S_FALSE;
1028 }
1029 
1030 #ifdef __REACTOS__
1031 HRESULT
1032 APIENTRY
1033 PathCchStripToRoot(
1034     _Inout_updates_(size) PWSTR path,
1035     _In_ size_t size)
1036 #else
1037 HRESULT WINAPI PathCchStripToRoot(WCHAR *path, SIZE_T size)
1038 #endif
1039 {
1040     const WCHAR *root_end;
1041     WCHAR *segment_end;
1042     BOOL is_unc;
1043 
1044     TRACE("%s %Iu\n", wine_dbgstr_w(path), size);
1045 
1046     if (!path || !*path || !size || size > PATHCCH_MAX_CCH) return E_INVALIDARG;
1047 
1048     /* \\\\?\\UNC\\* and \\\\* have to have at least two extra segments to be striped,
1049      * e.g. \\\\?\\UNC\\a\\b\\c -> \\\\?\\UNC\\a\\b
1050      *      \\\\a\\b\\c         -> \\\\a\\b         */
1051     if ((is_unc = is_prefixed_unc(path)) || (path[0] == '\\' && path[1] == '\\' && path[2] != '?'))
1052     {
1053         root_end = is_unc ? path + 8 : path + 3;
1054         if (!get_next_segment(root_end, &root_end)) return S_FALSE;
1055         if (!get_next_segment(root_end, &root_end)) return S_FALSE;
1056 
1057         if (root_end - path >= size) return E_INVALIDARG;
1058 
1059         segment_end = path + (root_end - path) - 1;
1060         *segment_end = 0;
1061         return S_OK;
1062     }
1063     else if (PathCchSkipRoot(path, &root_end) == S_OK)
1064     {
1065         if (root_end - path >= size) return E_INVALIDARG;
1066 
1067         segment_end = path + (root_end - path);
1068         if (!*segment_end) return S_FALSE;
1069 
1070         *segment_end = 0;
1071         return S_OK;
1072     }
1073     else
1074         return E_INVALIDARG;
1075 }
1076 
1077 #ifdef __REACTOS__
1078 BOOL
1079 APIENTRY
1080 PathIsUNCEx(
1081     _In_ PCWSTR path,
1082     _Outptr_opt_ PCWSTR* server)
1083 #else
1084 BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server)
1085 #endif
1086 {
1087     const WCHAR *result = NULL;
1088 
1089     TRACE("%s %p\n", wine_dbgstr_w(path), server);
1090 
1091     if (is_prefixed_unc(path))
1092         result = path + 8;
1093     else if (path[0] == '\\' && path[1] == '\\' && path[2] != '?')
1094         result = path + 2;
1095 
1096     if (server) *server = result;
1097     return !!result;
1098 }
1099