Coverage Report

Created: 2026-02-26 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */