Coverage Report

Created: 2026-09-01 06:58

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, MB_ERR_INVALID_CHARS,
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 tells 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, _TEXT("\\\\?\\"), 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;
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
295
HANDLE curlx_FindFirstFile(const char *filename,
296
                           WIN32_FIND_DATA *find_data)
297
{
298
  HANDLE handle = INVALID_HANDLE_VALUE;
299
300
#ifdef UNICODE
301
  TCHAR *filename_t = curlx_convert_UTF8_to_wchar(filename);
302
#else
303
  const TCHAR *filename_t = filename;
304
#endif
305
306
  if(filename_t) {
307
    TCHAR *fixed = NULL;
308
    const TCHAR *target;
309
310
    if(fix_excessive_path(filename_t, &fixed))
311
      target = fixed;
312
    else
313
      target = filename_t;
314
315
    handle = FindFirstFile(target, find_data);
316
    CURLX_FREE(fixed);
317
318
#ifdef UNICODE
319
    curlx_free(filename_t);
320
#endif
321
  }
322
323
  return handle;
324
}
325
#endif /* !CURL_WINDOWS_UWP */
326
327
int curlx_win32_open(const char *filename, int oflag, ...)
328
{
329
  int pmode = 0;
330
  int res = -1;
331
  TCHAR *fixed = NULL;
332
  const TCHAR *target = NULL;
333
334
#ifdef _UNICODE
335
  wchar_t *filename_w = fn_convert_UTF8_to_wchar(filename);
336
#endif
337
338
  va_list param;
339
  va_start(param, oflag);
340
  if(oflag & O_CREAT)
341
    pmode = va_arg(param, int);
342
  va_end(param);
343
344
#ifdef _UNICODE
345
  if(filename_w) {
346
    if(fix_excessive_path(filename_w, &fixed))
347
      target = fixed;
348
    else
349
      target = filename_w;
350
    errno = _wsopen_s(&res, target, oflag, _SH_DENYNO, pmode);
351
    CURLX_FREE(filename_w);
352
  }
353
  else
354
    /* !checksrc! disable ERRNOVAR 1 */
355
    errno = EINVAL;
356
#else
357
  if(fix_excessive_path(filename, &fixed))
358
    target = fixed;
359
  else
360
    target = filename;
361
  errno = _sopen_s(&res, target, oflag, _SH_DENYNO, pmode);
362
#endif
363
364
  CURLX_FREE(fixed);
365
  return res;
366
}
367
368
FILE *curlx_win32_fopen(const char *filename, const char *mode)
369
{
370
  FILE *file = NULL;
371
  TCHAR *fixed = NULL;
372
  const TCHAR *target = NULL;
373
374
#ifdef _UNICODE
375
  wchar_t *filename_w = fn_convert_UTF8_to_wchar(filename);
376
  wchar_t *mode_w = fn_convert_UTF8_to_wchar(mode);
377
  if(filename_w && mode_w) {
378
    if(fix_excessive_path(filename_w, &fixed))
379
      target = fixed;
380
    else
381
      target = filename_w;
382
    file = _wfsopen(target, mode_w, _SH_DENYNO);
383
  }
384
  else
385
    /* !checksrc! disable ERRNOVAR 1 */
386
    errno = EINVAL;
387
  CURLX_FREE(filename_w);
388
  CURLX_FREE(mode_w);
389
#else
390
  if(fix_excessive_path(filename, &fixed))
391
    target = fixed;
392
  else
393
    target = filename;
394
  file = _fsopen(target, mode, _SH_DENYNO);
395
#endif
396
397
  CURLX_FREE(fixed);
398
  return file;
399
}
400
401
#if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR < 5)
402
_CRTIMP errno_t __cdecl freopen_s(FILE **file, const char *filename,
403
                                  const char *mode, FILE *stream);
404
#endif
405
406
FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fp)
407
{
408
  FILE *file = NULL;
409
  TCHAR *fixed = NULL;
410
  const TCHAR *target = NULL;
411
412
#ifdef _UNICODE
413
  wchar_t *filename_w = fn_convert_UTF8_to_wchar(filename);
414
  wchar_t *mode_w = fn_convert_UTF8_to_wchar(mode);
415
  if(filename_w && mode_w) {
416
    if(fix_excessive_path(filename_w, &fixed))
417
      target = fixed;
418
    else
419
      target = filename_w;
420
    errno = _wfreopen_s(&file, target, mode_w, fp);
421
  }
422
  else
423
    /* !checksrc! disable ERRNOVAR 1 */
424
    errno = EINVAL;
425
  CURLX_FREE(filename_w);
426
  CURLX_FREE(mode_w);
427
#else
428
  if(fix_excessive_path(filename, &fixed))
429
    target = fixed;
430
  else
431
    target = filename;
432
  errno = freopen_s(&file, target, mode, fp);
433
#endif
434
435
  CURLX_FREE(fixed);
436
  return file;
437
}
438
439
int curlx_win32_stat(const char *path, curlx_struct_stat *buffer)
440
{
441
  int res = -1;
442
  TCHAR *fixed = NULL;
443
  const TCHAR *target = NULL;
444
445
#ifdef _UNICODE
446
  wchar_t *path_w = curlx_convert_UTF8_to_wchar(path);
447
  if(path_w) {
448
    if(fix_excessive_path(path_w, &fixed))
449
      target = fixed;
450
    else
451
      target = path_w;
452
    res = _wstati64(target, buffer);
453
    curlx_free(path_w);
454
  }
455
  else
456
    /* !checksrc! disable ERRNOVAR 1 */
457
    errno = EINVAL;
458
#else
459
  if(fix_excessive_path(path, &fixed))
460
    target = fixed;
461
  else
462
    target = path;
463
  res = _stati64(target, buffer);
464
#endif
465
466
  CURLX_FREE(fixed);
467
  return res;
468
}
469
470
#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES) || \
471
  !defined(CURL_DISABLE_ALTSVC)
472
/* rename() on Windows does not overwrite, so we cannot use it here.
473
   MoveFileEx() does overwrite and is usually atomic but fails when there are
474
   open handles to the file. */
475
int curlx_win32_rename(const char *oldpath, const char *newpath)
476
{
477
  int res = -1; /* fail */
478
479
#ifdef UNICODE
480
  TCHAR *tchar_oldpath = curlx_convert_UTF8_to_wchar(oldpath);
481
  TCHAR *tchar_newpath = curlx_convert_UTF8_to_wchar(newpath);
482
#else
483
  const TCHAR *tchar_oldpath = oldpath;
484
  const TCHAR *tchar_newpath = newpath;
485
#endif
486
487
  if(tchar_oldpath && tchar_newpath) {
488
    const int max_wait_ms = 1000;
489
    struct curltime start;
490
491
    TCHAR *oldpath_fixed = NULL;
492
    TCHAR *newpath_fixed = NULL;
493
    const TCHAR *target_oldpath;
494
    const TCHAR *target_newpath;
495
496
    if(fix_excessive_path(tchar_oldpath, &oldpath_fixed))
497
      target_oldpath = oldpath_fixed;
498
    else
499
      target_oldpath = tchar_oldpath;
500
501
    if(fix_excessive_path(tchar_newpath, &newpath_fixed))
502
      target_newpath = newpath_fixed;
503
    else
504
      target_newpath = tchar_newpath;
505
506
    start = curlx_now();
507
508
    for(;;) {
509
      timediff_t diff;
510
      /* !checksrc! disable BANNEDFUNC 1 */
511
      if(MoveFileEx(target_oldpath, target_newpath,
512
                    MOVEFILE_REPLACE_EXISTING)) {
513
        res = 0; /* success */
514
        break;
515
      }
516
      diff = curlx_timediff_ms(curlx_now(), start);
517
      if(diff < 0 || diff > max_wait_ms) {
518
        break;
519
      }
520
      Sleep(1);
521
    }
522
523
    CURLX_FREE(oldpath_fixed);
524
    CURLX_FREE(newpath_fixed);
525
  }
526
527
#ifdef UNICODE
528
  curlx_free(tchar_oldpath);
529
  curlx_free(tchar_newpath);
530
#endif
531
532
  return res;
533
}
534
#endif
535
536
#undef CURLX_MALLOC
537
#undef CURLX_FREE
538
539
#endif /* _WIN32 */