/src/curl/lib/curlx/win32-fopen.c
Line | Count | Source |
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 | | #include "curl_setup.h" |
25 | | |
26 | | #include "curlx/win32-fopen.h" |
27 | | |
28 | | int curlx_fseek(void *stream, curl_off_t offset, int whence) |
29 | 0 | { |
30 | | #ifdef _WIN32 |
31 | | return _fseeki64(stream, (__int64)offset, whence); |
32 | | #elif defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO) |
33 | | return fseeko(stream, (off_t)offset, whence); |
34 | | #else |
35 | | if(offset > LONG_MAX) |
36 | | return -1; |
37 | | return fseek(stream, (long)offset, whence); |
38 | | #endif |
39 | 0 | } |
40 | | |
41 | | #ifdef _WIN32 |
42 | | |
43 | | #include <direct.h> /* for _mkdir(), _wmkdir() */ |
44 | | #include <share.h> /* for _SH_DENYNO */ |
45 | | |
46 | | #include "curlx/multibyte.h" |
47 | | #include "curlx/timeval.h" |
48 | | |
49 | | #ifdef CURL_MEMDEBUG |
50 | | /* |
51 | | * Use system allocators to avoid infinite recursion when called by curl's |
52 | | * memory tracker memdebug functions. |
53 | | */ |
54 | | #define CURLX_MALLOC(x) malloc(x) |
55 | | #define CURLX_FREE(x) free(x) |
56 | | #else |
57 | | #define CURLX_MALLOC(x) curlx_malloc(x) |
58 | | #define CURLX_FREE(x) curlx_free(x) |
59 | | #endif |
60 | | |
61 | | #ifdef _UNICODE |
62 | | static wchar_t *fn_convert_UTF8_to_wchar(const char *str_utf8) |
63 | | { |
64 | | wchar_t *str_w = NULL; |
65 | | |
66 | | if(str_utf8) { |
67 | | int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, |
68 | | str_utf8, -1, NULL, 0); |
69 | | if(str_w_len > 0) { |
70 | | str_w = CURLX_MALLOC(str_w_len * sizeof(wchar_t)); |
71 | | if(str_w) { |
72 | | if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, |
73 | | str_utf8, -1, str_w, str_w_len) == 0) { |
74 | | CURLX_FREE(str_w); |
75 | | return NULL; |
76 | | } |
77 | | } |
78 | | } |
79 | | } |
80 | | return str_w; |
81 | | } |
82 | | #endif |
83 | | |
84 | | /* declare GetFullPathNameW for mingw-w64 UWP builds targeting old Windows */ |
85 | | #if defined(CURL_WINDOWS_UWP) && defined(__MINGW32__) && \ |
86 | | (_WIN32_WINNT < _WIN32_WINNT_WIN10) |
87 | | WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR *); |
88 | | #endif |
89 | | |
90 | | /* Fix excessive paths (paths that exceed MAX_PATH length of 260). |
91 | | * |
92 | | * This is a helper function to fix paths that would exceed the MAX_PATH |
93 | | * limitation check done by Windows APIs. It does so by normalizing the passed |
94 | | * in filename or path 'in' to its full canonical path, and if that path is |
95 | | * longer than MAX_PATH then setting 'out' to "\\?\" prefix + that full path. |
96 | | * |
97 | | * For example 'in' filename255chars in current directory C:\foo\bar is |
98 | | * fixed as \\?\C:\foo\bar\filename255chars for 'out' which tells Windows |
99 | | * it is ok to access that filename even though the actual full path is longer |
100 | | * than 260 chars. |
101 | | * |
102 | | * For non-Unicode builds this function may fail sometimes because only the |
103 | | * Unicode versions of some Windows API functions can access paths longer than |
104 | | * MAX_PATH, for example GetFullPathNameW which is used in this function. When |
105 | | * the full path is then converted from Unicode to multibyte that fails if any |
106 | | * directories in the path contain characters not in the current codepage. |
107 | | */ |
108 | | static bool fix_excessive_path(const TCHAR *in, TCHAR **out) |
109 | | { |
110 | | size_t needed, count; |
111 | | const wchar_t *in_w; |
112 | | wchar_t *fbuf = NULL; |
113 | | |
114 | | /* MS-documented "approximate" limit for the maximum path length */ |
115 | | const size_t max_path_len = 32767; |
116 | | |
117 | | #ifndef _UNICODE |
118 | | wchar_t *ibuf = NULL; |
119 | | char *obuf = NULL; |
120 | | #endif |
121 | | |
122 | | *out = NULL; |
123 | | |
124 | | /* skip paths already normalized */ |
125 | | if(!_tcsncmp(in, _TEXT("\\\\?\\"), 4)) |
126 | | goto cleanup; |
127 | | |
128 | | #ifndef _UNICODE |
129 | | /* convert multibyte input to unicode */ |
130 | | if(mbstowcs_s(&needed, NULL, 0, in, 0)) |
131 | | goto cleanup; |
132 | | if(!needed || needed >= max_path_len) |
133 | | goto cleanup; |
134 | | ibuf = CURLX_MALLOC(needed * sizeof(wchar_t)); |
135 | | if(!ibuf) |
136 | | goto cleanup; |
137 | | if(mbstowcs_s(&count, ibuf, needed, in, needed - 1)) |
138 | | goto cleanup; |
139 | | if(count != needed) |
140 | | goto cleanup; |
141 | | in_w = ibuf; |
142 | | #else |
143 | | in_w = in; |
144 | | #endif |
145 | | |
146 | | /* GetFullPathNameW returns the normalized full path in unicode. It converts |
147 | | forward slashes to backslashes, processes .. to remove directory segments, |
148 | | etc. Unlike GetFullPathNameA it can process paths that exceed MAX_PATH. */ |
149 | | needed = (size_t)GetFullPathNameW(in_w, 0, NULL, NULL); |
150 | | if(!needed || needed > max_path_len) |
151 | | goto cleanup; |
152 | | /* skip paths that are not excessive and do not need modification */ |
153 | | if(needed <= MAX_PATH) |
154 | | goto cleanup; |
155 | | fbuf = CURLX_MALLOC(needed * sizeof(wchar_t)); |
156 | | if(!fbuf) |
157 | | goto cleanup; |
158 | | count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL); |
159 | | if(!count || count >= needed) |
160 | | goto cleanup; |
161 | | |
162 | | /* prepend \\?\ or \\?\UNC\ to the excessively long path. |
163 | | * |
164 | | * c:\longpath ---> \\?\c:\longpath |
165 | | * \\.\c:\longpath ---> \\?\c:\longpath |
166 | | * \\?\c:\longpath ---> \\?\c:\longpath (unchanged) |
167 | | * \\server\c$\longpath ---> \\?\UNC\server\c$\longpath |
168 | | * |
169 | | * https://learn.microsoft.com/dotnet/standard/io/file-path-formats |
170 | | */ |
171 | | if(!wcsncmp(fbuf, L"\\\\?\\", 4)) |
172 | | ; /* do nothing */ |
173 | | else if(!wcsncmp(fbuf, L"\\\\.\\", 4)) |
174 | | fbuf[2] = '?'; |
175 | | else if(!wcsncmp(fbuf, L"\\\\.", 3) || !wcsncmp(fbuf, L"\\\\?", 3)) { |
176 | | /* Unexpected, not UNC. The formatting doc does not allow this AFAICT. */ |
177 | | goto cleanup; |
178 | | } |
179 | | else { |
180 | | wchar_t *temp; |
181 | | |
182 | | if(!wcsncmp(fbuf, L"\\\\", 2)) { |
183 | | /* "\\?\UNC\" + full path without "\\" + null */ |
184 | | needed = 8 + (count - 2) + 1; |
185 | | if(needed > max_path_len) |
186 | | goto cleanup; |
187 | | |
188 | | temp = CURLX_MALLOC(needed * sizeof(wchar_t)); |
189 | | if(!temp) |
190 | | goto cleanup; |
191 | | |
192 | | if(wcsncpy_s(temp, needed, L"\\\\?\\UNC\\", 8)) { |
193 | | CURLX_FREE(temp); |
194 | | goto cleanup; |
195 | | } |
196 | | if(wcscpy_s(temp + 8, needed, fbuf + 2)) { |
197 | | CURLX_FREE(temp); |
198 | | goto cleanup; |
199 | | } |
200 | | } |
201 | | else { |
202 | | /* "\\?\" + full path + null */ |
203 | | needed = 4 + count + 1; |
204 | | if(needed > max_path_len) |
205 | | goto cleanup; |
206 | | |
207 | | temp = CURLX_MALLOC(needed * sizeof(wchar_t)); |
208 | | if(!temp) |
209 | | goto cleanup; |
210 | | |
211 | | if(wcsncpy_s(temp, needed, L"\\\\?\\", 4)) { |
212 | | CURLX_FREE(temp); |
213 | | goto cleanup; |
214 | | } |
215 | | if(wcscpy_s(temp + 4, needed, fbuf)) { |
216 | | CURLX_FREE(temp); |
217 | | goto cleanup; |
218 | | } |
219 | | } |
220 | | |
221 | | CURLX_FREE(fbuf); |
222 | | fbuf = temp; |
223 | | } |
224 | | |
225 | | #ifndef _UNICODE |
226 | | /* convert unicode full path to multibyte output */ |
227 | | if(wcstombs_s(&needed, NULL, 0, fbuf, 0)) |
228 | | goto cleanup; |
229 | | if(!needed || needed >= max_path_len) |
230 | | goto cleanup; |
231 | | obuf = CURLX_MALLOC(needed); |
232 | | if(!obuf) |
233 | | goto cleanup; |
234 | | if(wcstombs_s(&count, obuf, needed, fbuf, needed - 1)) |
235 | | goto cleanup; |
236 | | if(count != needed) |
237 | | goto cleanup; |
238 | | *out = obuf; |
239 | | obuf = NULL; |
240 | | #else |
241 | | *out = fbuf; |
242 | | fbuf = NULL; |
243 | | #endif |
244 | | |
245 | | cleanup: |
246 | | CURLX_FREE(fbuf); |
247 | | #ifndef _UNICODE |
248 | | CURLX_FREE(ibuf); |
249 | | CURLX_FREE(obuf); |
250 | | #endif |
251 | | return !!*out; |
252 | | } |
253 | | |
254 | | #ifndef CURL_WINDOWS_UWP |
255 | | HANDLE curlx_CreateFile(const char *filename, |
256 | | DWORD dwDesiredAccess, |
257 | | DWORD dwShareMode, |
258 | | LPSECURITY_ATTRIBUTES lpSecurityAttributes, |
259 | | DWORD dwCreationDisposition, |
260 | | DWORD dwFlagsAndAttributes, |
261 | | HANDLE hTemplateFile) |
262 | | { |
263 | | HANDLE handle = INVALID_HANDLE_VALUE; |
264 | | |
265 | | #ifdef UNICODE |
266 | | TCHAR *filename_t = curlx_convert_UTF8_to_wchar(filename); |
267 | | #else |
268 | | const TCHAR *filename_t = filename; |
269 | | #endif |
270 | | |
271 | | if(filename_t) { |
272 | | TCHAR *fixed = NULL; |
273 | | const TCHAR *target = NULL; |
274 | | |
275 | | if(fix_excessive_path(filename_t, &fixed)) |
276 | | target = fixed; |
277 | | else |
278 | | target = filename_t; |
279 | | /* !checksrc! disable BANNEDFUNC 1 */ |
280 | | handle = CreateFile(target, |
281 | | dwDesiredAccess, |
282 | | dwShareMode, |
283 | | lpSecurityAttributes, |
284 | | dwCreationDisposition, |
285 | | dwFlagsAndAttributes, |
286 | | hTemplateFile); |
287 | | CURLX_FREE(fixed); |
288 | | #ifdef UNICODE |
289 | | curlx_free(filename_t); |
290 | | #endif |
291 | | } |
292 | | |
293 | | return handle; |
294 | | } |
295 | | |
296 | | HANDLE curlx_FindFirstFile(const char *filename, |
297 | | WIN32_FIND_DATA *find_data) |
298 | | { |
299 | | HANDLE handle = INVALID_HANDLE_VALUE; |
300 | | |
301 | | #ifdef UNICODE |
302 | | TCHAR *filename_t = curlx_convert_UTF8_to_wchar(filename); |
303 | | #else |
304 | | const TCHAR *filename_t = filename; |
305 | | #endif |
306 | | |
307 | | if(filename_t) { |
308 | | TCHAR *fixed = NULL; |
309 | | const TCHAR *target; |
310 | | |
311 | | if(fix_excessive_path(filename_t, &fixed)) |
312 | | target = fixed; |
313 | | else |
314 | | target = filename_t; |
315 | | |
316 | | handle = FindFirstFile(target, find_data); |
317 | | CURLX_FREE(fixed); |
318 | | |
319 | | #ifdef UNICODE |
320 | | curlx_free(filename_t); |
321 | | #endif |
322 | | } |
323 | | |
324 | | return handle; |
325 | | } |
326 | | #endif /* !CURL_WINDOWS_UWP */ |
327 | | |
328 | | int curlx_win32_open(const char *filename, int oflag, ...) |
329 | | { |
330 | | int pmode = 0; |
331 | | int res = -1; |
332 | | TCHAR *fixed = NULL; |
333 | | const TCHAR *target = NULL; |
334 | | |
335 | | #ifdef _UNICODE |
336 | | wchar_t *filename_w = fn_convert_UTF8_to_wchar(filename); |
337 | | #endif |
338 | | |
339 | | va_list param; |
340 | | va_start(param, oflag); |
341 | | if(oflag & O_CREAT) |
342 | | pmode = va_arg(param, int); |
343 | | va_end(param); |
344 | | |
345 | | #ifdef _UNICODE |
346 | | if(filename_w) { |
347 | | if(fix_excessive_path(filename_w, &fixed)) |
348 | | target = fixed; |
349 | | else |
350 | | target = filename_w; |
351 | | errno = _wsopen_s(&res, target, oflag, _SH_DENYNO, pmode); |
352 | | CURLX_FREE(filename_w); |
353 | | } |
354 | | else |
355 | | /* !checksrc! disable ERRNOVAR 1 */ |
356 | | errno = EINVAL; |
357 | | #else |
358 | | if(fix_excessive_path(filename, &fixed)) |
359 | | target = fixed; |
360 | | else |
361 | | target = filename; |
362 | | errno = _sopen_s(&res, target, oflag, _SH_DENYNO, pmode); |
363 | | #endif |
364 | | |
365 | | CURLX_FREE(fixed); |
366 | | return res; |
367 | | } |
368 | | |
369 | | FILE *curlx_win32_fopen(const char *filename, const char *mode) |
370 | | { |
371 | | FILE *file = NULL; |
372 | | TCHAR *fixed = NULL; |
373 | | const TCHAR *target = NULL; |
374 | | |
375 | | #ifdef _UNICODE |
376 | | wchar_t *filename_w = fn_convert_UTF8_to_wchar(filename); |
377 | | wchar_t *mode_w = fn_convert_UTF8_to_wchar(mode); |
378 | | if(filename_w && mode_w) { |
379 | | if(fix_excessive_path(filename_w, &fixed)) |
380 | | target = fixed; |
381 | | else |
382 | | target = filename_w; |
383 | | file = _wfsopen(target, mode_w, _SH_DENYNO); |
384 | | } |
385 | | else |
386 | | /* !checksrc! disable ERRNOVAR 1 */ |
387 | | errno = EINVAL; |
388 | | CURLX_FREE(filename_w); |
389 | | CURLX_FREE(mode_w); |
390 | | #else |
391 | | if(fix_excessive_path(filename, &fixed)) |
392 | | target = fixed; |
393 | | else |
394 | | target = filename; |
395 | | file = _fsopen(target, mode, _SH_DENYNO); |
396 | | #endif |
397 | | |
398 | | CURLX_FREE(fixed); |
399 | | return file; |
400 | | } |
401 | | |
402 | | #if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR < 5) |
403 | | _CRTIMP errno_t __cdecl freopen_s(FILE **file, const char *filename, |
404 | | const char *mode, FILE *stream); |
405 | | #endif |
406 | | |
407 | | FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fp) |
408 | | { |
409 | | FILE *file = NULL; |
410 | | TCHAR *fixed = NULL; |
411 | | const TCHAR *target = NULL; |
412 | | |
413 | | #ifdef _UNICODE |
414 | | wchar_t *filename_w = fn_convert_UTF8_to_wchar(filename); |
415 | | wchar_t *mode_w = fn_convert_UTF8_to_wchar(mode); |
416 | | if(filename_w && mode_w) { |
417 | | if(fix_excessive_path(filename_w, &fixed)) |
418 | | target = fixed; |
419 | | else |
420 | | target = filename_w; |
421 | | errno = _wfreopen_s(&file, target, mode_w, fp); |
422 | | } |
423 | | else |
424 | | /* !checksrc! disable ERRNOVAR 1 */ |
425 | | errno = EINVAL; |
426 | | CURLX_FREE(filename_w); |
427 | | CURLX_FREE(mode_w); |
428 | | #else |
429 | | if(fix_excessive_path(filename, &fixed)) |
430 | | target = fixed; |
431 | | else |
432 | | target = filename; |
433 | | errno = freopen_s(&file, target, mode, fp); |
434 | | #endif |
435 | | |
436 | | CURLX_FREE(fixed); |
437 | | return file; |
438 | | } |
439 | | |
440 | | int curlx_win32_stat(const char *path, curlx_struct_stat *buffer) |
441 | | { |
442 | | int res = -1; |
443 | | TCHAR *fixed = NULL; |
444 | | const TCHAR *target = NULL; |
445 | | |
446 | | #ifdef _UNICODE |
447 | | wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); |
448 | | if(path_w) { |
449 | | if(fix_excessive_path(path_w, &fixed)) |
450 | | target = fixed; |
451 | | else |
452 | | target = path_w; |
453 | | res = _wstati64(target, buffer); |
454 | | curlx_free(path_w); |
455 | | } |
456 | | else |
457 | | /* !checksrc! disable ERRNOVAR 1 */ |
458 | | errno = EINVAL; |
459 | | #else |
460 | | if(fix_excessive_path(path, &fixed)) |
461 | | target = fixed; |
462 | | else |
463 | | target = path; |
464 | | res = _stati64(target, buffer); |
465 | | #endif |
466 | | |
467 | | CURLX_FREE(fixed); |
468 | | return res; |
469 | | } |
470 | | |
471 | | int curlx_win32_mkdir(const char *path) |
472 | | { |
473 | | int res = -1; |
474 | | TCHAR *fixed = NULL; |
475 | | const TCHAR *target = NULL; |
476 | | |
477 | | #ifdef _UNICODE |
478 | | wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); |
479 | | if(path_w) { |
480 | | if(fix_excessive_path(path_w, &fixed)) |
481 | | target = fixed; |
482 | | else |
483 | | target = path_w; |
484 | | res = _wmkdir(target); |
485 | | curlx_free(path_w); |
486 | | } |
487 | | else |
488 | | /* !checksrc! disable ERRNOVAR 1 */ |
489 | | errno = EINVAL; |
490 | | #else |
491 | | if(fix_excessive_path(path, &fixed)) |
492 | | target = fixed; |
493 | | else |
494 | | target = path; |
495 | | res = _mkdir(target); |
496 | | #endif |
497 | | |
498 | | CURLX_FREE(fixed); |
499 | | return res; |
500 | | } |
501 | | |
502 | | #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES) || \ |
503 | | !defined(CURL_DISABLE_ALTSVC) |
504 | | /* rename() on Windows does not overwrite, so we cannot use it here. |
505 | | MoveFileEx() does overwrite and is usually atomic but fails when there are |
506 | | open handles to the file. */ |
507 | | int curlx_win32_rename(const char *oldpath, const char *newpath) |
508 | | { |
509 | | int res = -1; /* fail */ |
510 | | |
511 | | #ifdef UNICODE |
512 | | TCHAR *tchar_oldpath = curlx_convert_UTF8_to_wchar(oldpath); |
513 | | TCHAR *tchar_newpath = curlx_convert_UTF8_to_wchar(newpath); |
514 | | #else |
515 | | const TCHAR *tchar_oldpath = oldpath; |
516 | | const TCHAR *tchar_newpath = newpath; |
517 | | #endif |
518 | | |
519 | | if(tchar_oldpath && tchar_newpath) { |
520 | | const int max_wait_ms = 1000; |
521 | | struct curltime start; |
522 | | |
523 | | TCHAR *oldpath_fixed = NULL; |
524 | | TCHAR *newpath_fixed = NULL; |
525 | | const TCHAR *target_oldpath; |
526 | | const TCHAR *target_newpath; |
527 | | |
528 | | if(fix_excessive_path(tchar_oldpath, &oldpath_fixed)) |
529 | | target_oldpath = oldpath_fixed; |
530 | | else |
531 | | target_oldpath = tchar_oldpath; |
532 | | |
533 | | if(fix_excessive_path(tchar_newpath, &newpath_fixed)) |
534 | | target_newpath = newpath_fixed; |
535 | | else |
536 | | target_newpath = tchar_newpath; |
537 | | |
538 | | start = curlx_now(); |
539 | | |
540 | | for(;;) { |
541 | | timediff_t diff; |
542 | | /* !checksrc! disable BANNEDFUNC 1 */ |
543 | | if(MoveFileEx(target_oldpath, target_newpath, |
544 | | MOVEFILE_REPLACE_EXISTING)) { |
545 | | res = 0; /* success */ |
546 | | break; |
547 | | } |
548 | | diff = curlx_timediff_ms(curlx_now(), start); |
549 | | if(diff < 0 || diff > max_wait_ms) { |
550 | | break; |
551 | | } |
552 | | Sleep(1); |
553 | | } |
554 | | |
555 | | CURLX_FREE(oldpath_fixed); |
556 | | CURLX_FREE(newpath_fixed); |
557 | | } |
558 | | |
559 | | #ifdef UNICODE |
560 | | curlx_free(tchar_oldpath); |
561 | | curlx_free(tchar_newpath); |
562 | | #endif |
563 | | |
564 | | return res; |
565 | | } |
566 | | #endif |
567 | | |
568 | | #undef CURLX_MALLOC |
569 | | #undef CURLX_FREE |
570 | | |
571 | | #endif /* _WIN32 */ |