1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 /*
26 * This file is 'mem-include-scan' clean, which means memdebug.h and
27 * curl_memory.h are purposely not included in this file. See test 1132.
28 *
29 * The functions in this file are curlx functions which are not tracked by the
30 * curl memory tracker memdebug.
31 */
32
33 #include "curl_setup.h"
34
35 #ifdef _WIN32
36
37 #include "curl_multibyte.h"
38
39 /*
40 * MultiByte conversions using Windows kernel32 library.
41 */
42
curlx_convert_UTF8_to_wchar(const char * str_utf8)43 wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8)
44 {
45 wchar_t *str_w = NULL;
46
47 if(str_utf8) {
48 int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
49 str_utf8, -1, NULL, 0);
50 if(str_w_len > 0) {
51 str_w = malloc(str_w_len * sizeof(wchar_t));
52 if(str_w) {
53 if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w,
54 str_w_len) == 0) {
55 free(str_w);
56 return NULL;
57 }
58 }
59 }
60 }
61
62 return str_w;
63 }
64
curlx_convert_wchar_to_UTF8(const wchar_t * str_w)65 char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w)
66 {
67 char *str_utf8 = NULL;
68
69 if(str_w) {
70 int bytes = WideCharToMultiByte(CP_UTF8, 0, str_w, -1,
71 NULL, 0, NULL, NULL);
72 if(bytes > 0) {
73 str_utf8 = malloc(bytes);
74 if(str_utf8) {
75 if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, bytes,
76 NULL, NULL) == 0) {
77 free(str_utf8);
78 return NULL;
79 }
80 }
81 }
82 }
83
84 return str_utf8;
85 }
86
87 /* declare GetFullPathNameW for mingw-w64 UWP builds targeting old windows */
88 #if defined(CURL_WINDOWS_UWP) && defined(__MINGW32__) && \
89 (_WIN32_WINNT < _WIN32_WINNT_WIN10)
90 WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR *);
91 #endif
92
93 /* Fix excessive paths (paths that exceed MAX_PATH length of 260).
94 *
95 * This is a helper function to fix paths that would exceed the MAX_PATH
96 * limitation check done by Windows APIs. It does so by normalizing the passed
97 * in filename or path 'in' to its full canonical path, and if that path is
98 * longer than MAX_PATH then setting 'out' to "\\?\" prefix + that full path.
99 *
100 * For example 'in' filename255chars in current directory C:\foo\bar is
101 * fixed as \\?\C:\foo\bar\filename255chars for 'out' which will tell Windows
102 * it is ok to access that filename even though the actual full path is longer
103 * than 260 chars.
104 *
105 * For non-Unicode builds this function may fail sometimes because only the
106 * Unicode versions of some Windows API functions can access paths longer than
107 * MAX_PATH, for example GetFullPathNameW which is used in this function. When
108 * the full path is then converted from Unicode to multibyte that fails if any
109 * directories in the path contain characters not in the current codepage.
110 */
fix_excessive_path(const TCHAR * in,TCHAR ** out)111 static bool fix_excessive_path(const TCHAR *in, TCHAR **out)
112 {
113 size_t needed, count;
114 const wchar_t *in_w;
115 wchar_t *fbuf = NULL;
116
117 /* MS documented "approximate" limit for the maximum path length */
118 const size_t max_path_len = 32767;
119
120 #ifndef _UNICODE
121 wchar_t *ibuf = NULL;
122 char *obuf = NULL;
123 #endif
124
125 *out = NULL;
126
127 /* skip paths already normalized */
128 if(!_tcsncmp(in, _T("\\\\?\\"), 4))
129 goto cleanup;
130
131 #ifndef _UNICODE
132 /* convert multibyte input to unicode */
133 needed = mbstowcs(NULL, in, 0);
134 if(needed == (size_t)-1 || needed >= max_path_len)
135 goto cleanup;
136 ++needed; /* for NUL */
137 ibuf = malloc(needed * sizeof(wchar_t));
138 if(!ibuf)
139 goto cleanup;
140 count = mbstowcs(ibuf, in, needed);
141 if(count == (size_t)-1 || count >= needed)
142 goto cleanup;
143 in_w = ibuf;
144 #else
145 in_w = in;
146 #endif
147
148 /* GetFullPathNameW returns the normalized full path in unicode. It converts
149 forward slashes to backslashes, processes .. to remove directory segments,
150 etc. Unlike GetFullPathNameA it can process paths that exceed MAX_PATH. */
151 needed = (size_t)GetFullPathNameW(in_w, 0, NULL, NULL);
152 if(!needed || needed > max_path_len)
153 goto cleanup;
154 /* skip paths that are not excessive and do not need modification */
155 if(needed <= MAX_PATH)
156 goto cleanup;
157 fbuf = malloc(needed * sizeof(wchar_t));
158 if(!fbuf)
159 goto cleanup;
160 count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL);
161 if(!count || count >= needed)
162 goto cleanup;
163
164 /* prepend \\?\ or \\?\UNC\ to the excessively long path.
165 *
166 * c:\longpath ---> \\?\c:\longpath
167 * \\.\c:\longpath ---> \\?\c:\longpath
168 * \\?\c:\longpath ---> \\?\c:\longpath (unchanged)
169 * \\server\c$\longpath ---> \\?\UNC\server\c$\longpath
170 *
171 * https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
172 */
173 if(!wcsncmp(fbuf, L"\\\\?\\", 4))
174 ; /* do nothing */
175 else if(!wcsncmp(fbuf, L"\\\\.\\", 4))
176 fbuf[2] = '?';
177 else if(!wcsncmp(fbuf, L"\\\\.", 3) || !wcsncmp(fbuf, L"\\\\?", 3)) {
178 /* Unexpected, not UNC. The formatting doc doesn't allow this AFAICT. */
179 goto cleanup;
180 }
181 else {
182 wchar_t *temp;
183
184 if(!wcsncmp(fbuf, L"\\\\", 2)) {
185 /* "\\?\UNC\" + full path without "\\" + null */
186 needed = 8 + (count - 2) + 1;
187 if(needed > max_path_len)
188 goto cleanup;
189
190 temp = malloc(needed * sizeof(wchar_t));
191 if(!temp)
192 goto cleanup;
193
194 wcsncpy(temp, L"\\\\?\\UNC\\", 8);
195 wcscpy(temp + 8, fbuf + 2);
196 }
197 else {
198 /* "\\?\" + full path + null */
199 needed = 4 + count + 1;
200 if(needed > max_path_len)
201 goto cleanup;
202
203 temp = malloc(needed * sizeof(wchar_t));
204 if(!temp)
205 goto cleanup;
206
207 wcsncpy(temp, L"\\\\?\\", 4);
208 wcscpy(temp + 4, fbuf);
209 }
210
211 free(fbuf);
212 fbuf = temp;
213 }
214
215 #ifndef _UNICODE
216 /* convert unicode full path to multibyte output */
217 needed = wcstombs(NULL, fbuf, 0);
218 if(needed == (size_t)-1 || needed >= max_path_len)
219 goto cleanup;
220 ++needed; /* for NUL */
221 obuf = malloc(needed);
222 if(!obuf)
223 goto cleanup;
224 count = wcstombs(obuf, fbuf, needed);
225 if(count == (size_t)-1 || count >= needed)
226 goto cleanup;
227 *out = obuf;
228 obuf = NULL;
229 #else
230 *out = fbuf;
231 fbuf = NULL;
232 #endif
233
234 cleanup:
235 free(fbuf);
236 #ifndef _UNICODE
237 free(ibuf);
238 free(obuf);
239 #endif
240 return *out ? true : false;
241 }
242
curlx_win32_open(const char * filename,int oflag,...)243 int curlx_win32_open(const char *filename, int oflag, ...)
244 {
245 int pmode = 0;
246 int result = -1;
247 TCHAR *fixed = NULL;
248 const TCHAR *target = NULL;
249
250 #ifdef _UNICODE
251 wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
252 #endif
253
254 va_list param;
255 va_start(param, oflag);
256 if(oflag & O_CREAT)
257 pmode = va_arg(param, int);
258 va_end(param);
259
260 #ifdef _UNICODE
261 if(filename_w) {
262 if(fix_excessive_path(filename_w, &fixed))
263 target = fixed;
264 else
265 target = filename_w;
266 result = _wopen(target, oflag, pmode);
267 curlx_unicodefree(filename_w);
268 }
269 else
270 errno = EINVAL;
271 #else
272 if(fix_excessive_path(filename, &fixed))
273 target = fixed;
274 else
275 target = filename;
276 result = (_open)(target, oflag, pmode);
277 #endif
278
279 free(fixed);
280 return result;
281 }
282
curlx_win32_fopen(const char * filename,const char * mode)283 FILE *curlx_win32_fopen(const char *filename, const char *mode)
284 {
285 FILE *result = NULL;
286 TCHAR *fixed = NULL;
287 const TCHAR *target = NULL;
288
289 #ifdef _UNICODE
290 wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
291 wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode);
292 if(filename_w && mode_w) {
293 if(fix_excessive_path(filename_w, &fixed))
294 target = fixed;
295 else
296 target = filename_w;
297 result = _wfopen(target, mode_w);
298 }
299 else
300 errno = EINVAL;
301 curlx_unicodefree(filename_w);
302 curlx_unicodefree(mode_w);
303 #else
304 if(fix_excessive_path(filename, &fixed))
305 target = fixed;
306 else
307 target = filename;
308 result = (fopen)(target, mode);
309 #endif
310
311 free(fixed);
312 return result;
313 }
314
curlx_win32_stat(const char * path,struct_stat * buffer)315 int curlx_win32_stat(const char *path, struct_stat *buffer)
316 {
317 int result = -1;
318 TCHAR *fixed = NULL;
319 const TCHAR *target = NULL;
320
321 #ifdef _UNICODE
322 wchar_t *path_w = curlx_convert_UTF8_to_wchar(path);
323 if(path_w) {
324 if(fix_excessive_path(path_w, &fixed))
325 target = fixed;
326 else
327 target = path_w;
328 #ifndef USE_WIN32_LARGE_FILES
329 result = _wstat(target, buffer);
330 #else
331 result = _wstati64(target, buffer);
332 #endif
333 curlx_unicodefree(path_w);
334 }
335 else
336 errno = EINVAL;
337 #else
338 if(fix_excessive_path(path, &fixed))
339 target = fixed;
340 else
341 target = path;
342 #ifndef USE_WIN32_LARGE_FILES
343 result = _stat(target, buffer);
344 #else
345 result = _stati64(target, buffer);
346 #endif
347 #endif
348
349 free(fixed);
350 return result;
351 }
352
353 #endif /* _WIN32 */
354