Coverage Report

Created: 2025-11-23 06:13

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
25
/*
26
 * This file is 'mem-include-scan' clean, which means its memory allocations
27
 * are not tracked by the curl memory tracker memdebug, so they must not use
28
 * `CURLDEBUG` macro replacements in memdebug.h for free, malloc, etc. To avoid
29
 * these macro replacements, wrap the names in parentheses to call the original
30
 * versions: `ptr = (malloc)(123)`, `(free)(ptr)`, etc.
31
 */
32
33
#include "../curl_setup.h"
34
35
#include "fopen.h"
36
37
int curlx_fseek(void *stream, curl_off_t offset, int whence)
38
0
{
39
#if defined(_WIN32) && defined(USE_WIN32_LARGE_FILES)
40
  return _fseeki64(stream, (__int64)offset, whence);
41
#elif defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO)
42
  return fseeko(stream, (off_t)offset, whence);
43
#else
44
  if(offset > LONG_MAX)
45
    return -1;
46
  return fseek(stream, (long)offset, whence);
47
#endif
48
0
}
49
50
#ifdef _WIN32
51
52
#include "multibyte.h"
53
54
/* declare GetFullPathNameW for mingw-w64 UWP builds targeting old windows */
55
#if defined(CURL_WINDOWS_UWP) && defined(__MINGW32__) && \
56
  (_WIN32_WINNT < _WIN32_WINNT_WIN10)
57
WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR *);
58
#endif
59
60
/* Fix excessive paths (paths that exceed MAX_PATH length of 260).
61
 *
62
 * This is a helper function to fix paths that would exceed the MAX_PATH
63
 * limitation check done by Windows APIs. It does so by normalizing the passed
64
 * in filename or path 'in' to its full canonical path, and if that path is
65
 * longer than MAX_PATH then setting 'out' to "\\?\" prefix + that full path.
66
 *
67
 * For example 'in' filename255chars in current directory C:\foo\bar is
68
 * fixed as \\?\C:\foo\bar\filename255chars for 'out' which will tell Windows
69
 * it is ok to access that filename even though the actual full path is longer
70
 * than 260 chars.
71
 *
72
 * For non-Unicode builds this function may fail sometimes because only the
73
 * Unicode versions of some Windows API functions can access paths longer than
74
 * MAX_PATH, for example GetFullPathNameW which is used in this function. When
75
 * the full path is then converted from Unicode to multibyte that fails if any
76
 * directories in the path contain characters not in the current codepage.
77
 */
78
static bool fix_excessive_path(const TCHAR *in, TCHAR **out)
79
{
80
  size_t needed, count;
81
  const wchar_t *in_w;
82
  wchar_t *fbuf = NULL;
83
84
  /* MS documented "approximate" limit for the maximum path length */
85
  const size_t max_path_len = 32767;
86
87
#ifndef _UNICODE
88
  wchar_t *ibuf = NULL;
89
  char *obuf = NULL;
90
#endif
91
92
  *out = NULL;
93
94
  /* skip paths already normalized */
95
  if(!_tcsncmp(in, _T("\\\\?\\"), 4))
96
    goto cleanup;
97
98
#ifndef _UNICODE
99
  /* convert multibyte input to unicode */
100
  if(mbstowcs_s(&needed, NULL, 0, in, 0))
101
    goto cleanup;
102
  if(!needed || needed >= max_path_len)
103
    goto cleanup;
104
  ibuf = (malloc)(needed * sizeof(wchar_t));
105
  if(!ibuf)
106
    goto cleanup;
107
  if(mbstowcs_s(&count, ibuf, needed, in, needed - 1))
108
    goto cleanup;
109
  if(count != needed)
110
    goto cleanup;
111
  in_w = ibuf;
112
#else
113
  in_w = in;
114
#endif
115
116
  /* GetFullPathNameW returns the normalized full path in unicode. It converts
117
     forward slashes to backslashes, processes .. to remove directory segments,
118
     etc. Unlike GetFullPathNameA it can process paths that exceed MAX_PATH. */
119
  needed = (size_t)GetFullPathNameW(in_w, 0, NULL, NULL);
120
  if(!needed || needed > max_path_len)
121
    goto cleanup;
122
  /* skip paths that are not excessive and do not need modification */
123
  if(needed <= MAX_PATH)
124
    goto cleanup;
125
  fbuf = (malloc)(needed * sizeof(wchar_t));
126
  if(!fbuf)
127
    goto cleanup;
128
  count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL);
129
  if(!count || count >= needed)
130
    goto cleanup;
131
132
  /* prepend \\?\ or \\?\UNC\ to the excessively long path.
133
   *
134
   * c:\longpath            --->    \\?\c:\longpath
135
   * \\.\c:\longpath        --->    \\?\c:\longpath
136
   * \\?\c:\longpath        --->    \\?\c:\longpath  (unchanged)
137
   * \\server\c$\longpath   --->    \\?\UNC\server\c$\longpath
138
   *
139
   * https://learn.microsoft.com/dotnet/standard/io/file-path-formats
140
   */
141
  if(!wcsncmp(fbuf, L"\\\\?\\", 4))
142
    ; /* do nothing */
143
  else if(!wcsncmp(fbuf, L"\\\\.\\", 4))
144
    fbuf[2] = '?';
145
  else if(!wcsncmp(fbuf, L"\\\\.", 3) || !wcsncmp(fbuf, L"\\\\?", 3)) {
146
    /* Unexpected, not UNC. The formatting doc does not allow this AFAICT. */
147
    goto cleanup;
148
  }
149
  else {
150
    wchar_t *temp;
151
152
    if(!wcsncmp(fbuf, L"\\\\", 2)) {
153
      /* "\\?\UNC\" + full path without "\\" + null */
154
      needed = 8 + (count - 2) + 1;
155
      if(needed > max_path_len)
156
        goto cleanup;
157
158
      temp = (malloc)(needed * sizeof(wchar_t));
159
      if(!temp)
160
        goto cleanup;
161
162
      if(wcsncpy_s(temp, needed, L"\\\\?\\UNC\\", 8)) {
163
        (free)(temp);
164
        goto cleanup;
165
      }
166
      if(wcscpy_s(temp + 8, needed, fbuf + 2)) {
167
        (free)(temp);
168
        goto cleanup;
169
      }
170
    }
171
    else {
172
      /* "\\?\" + full path + null */
173
      needed = 4 + count + 1;
174
      if(needed > max_path_len)
175
        goto cleanup;
176
177
      temp = (malloc)(needed * sizeof(wchar_t));
178
      if(!temp)
179
        goto cleanup;
180
181
      if(wcsncpy_s(temp, needed, L"\\\\?\\", 4)) {
182
        (free)(temp);
183
        goto cleanup;
184
      }
185
      if(wcscpy_s(temp + 4, needed, fbuf)) {
186
        (free)(temp);
187
        goto cleanup;
188
      }
189
    }
190
191
    (free)(fbuf);
192
    fbuf = temp;
193
  }
194
195
#ifndef _UNICODE
196
  /* convert unicode full path to multibyte output */
197
  if(wcstombs_s(&needed, NULL, 0, fbuf, 0))
198
    goto cleanup;
199
  if(!needed || needed >= max_path_len)
200
    goto cleanup;
201
  obuf = (malloc)(needed);
202
  if(!obuf)
203
    goto cleanup;
204
  if(wcstombs_s(&count, obuf, needed, fbuf, needed - 1))
205
    goto cleanup;
206
  if(count != needed)
207
    goto cleanup;
208
  *out = obuf;
209
  obuf = NULL;
210
#else
211
  *out = fbuf;
212
  fbuf = NULL;
213
#endif
214
215
cleanup:
216
  (free)(fbuf);
217
#ifndef _UNICODE
218
  (free)(ibuf);
219
  (free)(obuf);
220
#endif
221
  return *out ? true : false;
222
}
223
224
int curlx_win32_open(const char *filename, int oflag, ...)
225
{
226
  int pmode = 0;
227
  int result = -1;
228
  TCHAR *fixed = NULL;
229
  const TCHAR *target = NULL;
230
231
#ifdef _UNICODE
232
  wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
233
#endif
234
235
  va_list param;
236
  va_start(param, oflag);
237
  if(oflag & O_CREAT)
238
    pmode = va_arg(param, int);
239
  va_end(param);
240
241
#ifdef _UNICODE
242
  if(filename_w) {
243
    if(fix_excessive_path(filename_w, &fixed))
244
      target = fixed;
245
    else
246
      target = filename_w;
247
    result = _wopen(target, oflag, pmode);
248
    curlx_unicodefree(filename_w);
249
  }
250
  else
251
    /* !checksrc! disable ERRNOVAR 1 */
252
    errno = EINVAL;
253
#else
254
  if(fix_excessive_path(filename, &fixed))
255
    target = fixed;
256
  else
257
    target = filename;
258
  result = _open(target, oflag, pmode);
259
#endif
260
261
  (free)(fixed);
262
  return result;
263
}
264
265
FILE *curlx_win32_fopen(const char *filename, const char *mode)
266
{
267
  FILE *result = NULL;
268
  TCHAR *fixed = NULL;
269
  const TCHAR *target = NULL;
270
271
#ifdef _UNICODE
272
  wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
273
  wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode);
274
  if(filename_w && mode_w) {
275
    if(fix_excessive_path(filename_w, &fixed))
276
      target = fixed;
277
    else
278
      target = filename_w;
279
    result = _wfopen(target, mode_w);
280
  }
281
  else
282
    /* !checksrc! disable ERRNOVAR 1 */
283
    errno = EINVAL;
284
  curlx_unicodefree(filename_w);
285
  curlx_unicodefree(mode_w);
286
#else
287
  if(fix_excessive_path(filename, &fixed))
288
    target = fixed;
289
  else
290
    target = filename;
291
  /* !checksrc! disable BANNEDFUNC 1 */
292
  result = fopen(target, mode);
293
#endif
294
295
  (free)(fixed);
296
  return result;
297
}
298
299
FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fp)
300
{
301
  FILE *result = NULL;
302
  TCHAR *fixed = NULL;
303
  const TCHAR *target = NULL;
304
305
#ifdef _UNICODE
306
  wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
307
  wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode);
308
  if(filename_w && mode_w) {
309
    if(fix_excessive_path(filename_w, &fixed))
310
      target = fixed;
311
    else
312
      target = filename_w;
313
    result = _wfreopen(target, mode_w, fp);
314
  }
315
  else
316
    /* !checksrc! disable ERRNOVAR 1 */
317
    errno = EINVAL;
318
  curlx_unicodefree(filename_w);
319
  curlx_unicodefree(mode_w);
320
#else
321
  if(fix_excessive_path(filename, &fixed))
322
    target = fixed;
323
  else
324
    target = filename;
325
  /* !checksrc! disable BANNEDFUNC 1 */
326
  result = freopen(target, mode, fp);
327
#endif
328
329
  (free)(fixed);
330
  return result;
331
}
332
333
int curlx_win32_stat(const char *path, struct_stat *buffer)
334
{
335
  int result = -1;
336
  TCHAR *fixed = NULL;
337
  const TCHAR *target = NULL;
338
339
#ifdef _UNICODE
340
  wchar_t *path_w = curlx_convert_UTF8_to_wchar(path);
341
  if(path_w) {
342
    if(fix_excessive_path(path_w, &fixed))
343
      target = fixed;
344
    else
345
      target = path_w;
346
#ifndef USE_WIN32_LARGE_FILES
347
    result = _wstat(target, buffer);
348
#else
349
    result = _wstati64(target, buffer);
350
#endif
351
    curlx_unicodefree(path_w);
352
  }
353
  else
354
    /* !checksrc! disable ERRNOVAR 1 */
355
    errno = EINVAL;
356
#else
357
  if(fix_excessive_path(path, &fixed))
358
    target = fixed;
359
  else
360
    target = path;
361
#ifndef USE_WIN32_LARGE_FILES
362
  result = _stat(target, buffer);
363
#else
364
  result = _stati64(target, buffer);
365
#endif
366
#endif
367
368
  (free)(fixed);
369
  return result;
370
}
371
372
#endif /* _WIN32 */