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 */
hacked_wcsnlen(const wchar_t * str,size_t size)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
is_drive_spec(const WCHAR * str)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
is_prefixed_unc(const WCHAR * string)75 static BOOL is_prefixed_unc(const WCHAR *string)
76 {
77 return !wcsnicmp(string, L"\\\\?\\UNC\\", 8 );
78 }
79
is_prefixed_disk(const WCHAR * string)80 static BOOL is_prefixed_disk(const WCHAR *string)
81 {
82 return !wcsncmp(string, L"\\\\?\\", 4) && is_drive_spec( string + 4 );
83 }
84
is_prefixed_volume(const WCHAR * string)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 */
get_next_segment(const WCHAR * next,const WCHAR ** next_segment)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 */
get_root_end(const WCHAR * path)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
PathAllocCanonicalize(_In_ PCWSTR path_in,_In_ ULONG flags,_Outptr_ PWSTR * path_out)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
PathAllocCombine(_In_opt_ PCWSTR path1,_In_opt_ PCWSTR path2,_In_ ULONG flags,_Outptr_ PWSTR * out)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
PathCchAddBackslash(_Inout_updates_ (size)PWSTR path,_In_ size_t size)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
PathCchAddBackslashEx(_Inout_updates_ (size)PWSTR path,_In_ size_t size,_Outptr_opt_result_buffer_ (* remaining)PWSTR * endptr,_Out_opt_ size_t * remaining)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
PathCchAddExtension(_Inout_updates_ (size)PWSTR path,_In_ size_t size,_In_ PCWSTR extension)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
PathCchAppend(_Inout_updates_ (size)PWSTR path1,_In_ size_t size,_In_opt_ PCWSTR path2)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
PathCchAppendEx(_Inout_updates_ (size)PWSTR path1,_In_ size_t size,_In_opt_ PCWSTR path2,_In_ ULONG flags)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
PathCchCanonicalize(_Out_writes_ (size)PWSTR out,_In_ size_t size,_In_ PCWSTR in)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
PathCchCanonicalizeEx(_Out_writes_ (size)PWSTR out,_In_ size_t size,_In_ PCWSTR in,_In_ ULONG flags)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
PathCchCombine(_Out_writes_ (size)PWSTR out,_In_ size_t size,_In_opt_ PCWSTR path1,_In_opt_ PCWSTR path2)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
PathCchCombineEx(_Out_writes_ (size)PWSTR out,_In_ size_t size,_In_opt_ PCWSTR path1,_In_opt_ PCWSTR path2,_In_ ULONG flags)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
PathCchFindExtension(_In_reads_ (size)PCWSTR path,_In_ size_t size,_Outptr_ PCWSTR * extension)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
PathCchIsRoot(_In_opt_ PCWSTR path)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
PathCchRemoveBackslash(_Inout_updates_ (path_size)PWSTR path,_In_ size_t path_size)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
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)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
PathCchRemoveExtension(_Inout_updates_ (size)PWSTR path,_In_ size_t size)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
PathCchRemoveFileSpec(_Inout_updates_ (size)PWSTR path,_In_ size_t size)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
PathCchRenameExtension(_Inout_updates_ (size)PWSTR path,_In_ size_t size,_In_ PCWSTR extension)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
PathCchSkipRoot(_In_ PCWSTR path,_Outptr_ PCWSTR * root_end)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
PathCchStripPrefix(_Inout_updates_ (size)PWSTR path,_In_ size_t size)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
PathCchStripToRoot(_Inout_updates_ (size)PWSTR path,_In_ size_t size)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
PathIsUNCEx(_In_ PCWSTR path,_Outptr_opt_ PCWSTR * server)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