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