Coverage Report

Created: 2025-10-30 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/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
#if defined(_WIN32) && !defined(UNDER_CE)
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
  needed = mbstowcs(NULL, in, 0);
101
  if(needed == (size_t)-1 || needed >= max_path_len)
102
    goto cleanup;
103
  ++needed; /* for NUL */
104
  ibuf = (malloc)(needed * sizeof(wchar_t));
105
  if(!ibuf)
106
    goto cleanup;
107
  count = mbstowcs(ibuf, in, needed);
108
  if(count == (size_t)-1 || count >= needed)
109
    goto cleanup;
110
  in_w = ibuf;
111
#else
112
  in_w = in;
113
#endif
114
115
  /* GetFullPathNameW returns the normalized full path in unicode. It converts
116
     forward slashes to backslashes, processes .. to remove directory segments,
117
     etc. Unlike GetFullPathNameA it can process paths that exceed MAX_PATH. */
118
  needed = (size_t)GetFullPathNameW(in_w, 0, NULL, NULL);
119
  if(!needed || needed > max_path_len)
120
    goto cleanup;
121
  /* skip paths that are not excessive and do not need modification */
122
  if(needed <= MAX_PATH)
123
    goto cleanup;
124
  fbuf = (malloc)(needed * sizeof(wchar_t));
125
  if(!fbuf)
126
    goto cleanup;
127
  count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL);
128
  if(!count || count >= needed)
129
    goto cleanup;
130
131
  /* prepend \\?\ or \\?\UNC\ to the excessively long path.
132
   *
133
   * c:\longpath            --->    \\?\c:\longpath
134
   * \\.\c:\longpath        --->    \\?\c:\longpath
135
   * \\?\c:\longpath        --->    \\?\c:\longpath  (unchanged)
136
   * \\server\c$\longpath   --->    \\?\UNC\server\c$\longpath
137
   *
138
   * https://learn.microsoft.com/dotnet/standard/io/file-path-formats
139
   */
140
  if(!wcsncmp(fbuf, L"\\\\?\\", 4))
141
    ; /* do nothing */
142
  else if(!wcsncmp(fbuf, L"\\\\.\\", 4))
143
    fbuf[2] = '?';
144
  else if(!wcsncmp(fbuf, L"\\\\.", 3) || !wcsncmp(fbuf, L"\\\\?", 3)) {
145
    /* Unexpected, not UNC. The formatting doc doesn't allow this AFAICT. */
146
    goto cleanup;
147
  }
148
  else {
149
    wchar_t *temp;
150
151
    if(!wcsncmp(fbuf, L"\\\\", 2)) {
152
      /* "\\?\UNC\" + full path without "\\" + null */
153
      needed = 8 + (count - 2) + 1;
154
      if(needed > max_path_len)
155
        goto cleanup;
156
157
      temp = (malloc)(needed * sizeof(wchar_t));
158
      if(!temp)
159
        goto cleanup;
160
161
      wcsncpy(temp, L"\\\\?\\UNC\\", 8);
162
      wcscpy(temp + 8, fbuf + 2);
163
    }
164
    else {
165
      /* "\\?\" + full path + null */
166
      needed = 4 + count + 1;
167
      if(needed > max_path_len)
168
        goto cleanup;
169
170
      temp = (malloc)(needed * sizeof(wchar_t));
171
      if(!temp)
172
        goto cleanup;
173
174
      wcsncpy(temp, L"\\\\?\\", 4);
175
      wcscpy(temp + 4, fbuf);
176
    }
177
178
    (free)(fbuf);
179
    fbuf = temp;
180
  }
181
182
#ifndef _UNICODE
183
  /* convert unicode full path to multibyte output */
184
  needed = wcstombs(NULL, fbuf, 0);
185
  if(needed == (size_t)-1 || needed >= max_path_len)
186
    goto cleanup;
187
  ++needed; /* for NUL */
188
  obuf = (malloc)(needed);
189
  if(!obuf)
190
    goto cleanup;
191
  count = wcstombs(obuf, fbuf, needed);
192
  if(count == (size_t)-1 || count >= needed)
193
    goto cleanup;
194
  *out = obuf;
195
  obuf = NULL;
196
#else
197
  *out = fbuf;
198
  fbuf = NULL;
199
#endif
200
201
cleanup:
202
  (free)(fbuf);
203
#ifndef _UNICODE
204
  (free)(ibuf);
205
  (free)(obuf);
206
#endif
207
  return *out ? true : false;
208
}
209
210
int curlx_win32_open(const char *filename, int oflag, ...)
211
{
212
  int pmode = 0;
213
  int result = -1;
214
  TCHAR *fixed = NULL;
215
  const TCHAR *target = NULL;
216
217
#ifdef _UNICODE
218
  wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
219
#endif
220
221
  va_list param;
222
  va_start(param, oflag);
223
  if(oflag & O_CREAT)
224
    pmode = va_arg(param, int);
225
  va_end(param);
226
227
#ifdef _UNICODE
228
  if(filename_w) {
229
    if(fix_excessive_path(filename_w, &fixed))
230
      target = fixed;
231
    else
232
      target = filename_w;
233
    result = _wopen(target, oflag, pmode);
234
    curlx_unicodefree(filename_w);
235
  }
236
  else
237
    /* !checksrc! disable ERRNOVAR 1 */
238
    CURL_SETERRNO(EINVAL);
239
#else
240
  if(fix_excessive_path(filename, &fixed))
241
    target = fixed;
242
  else
243
    target = filename;
244
  result = _open(target, oflag, pmode);
245
#endif
246
247
  (free)(fixed);
248
  return result;
249
}
250
251
FILE *curlx_win32_fopen(const char *filename, const char *mode)
252
{
253
  FILE *result = NULL;
254
  TCHAR *fixed = NULL;
255
  const TCHAR *target = NULL;
256
257
#ifdef _UNICODE
258
  wchar_t *filename_w = curlx_convert_UTF8_to_wchar(filename);
259
  wchar_t *mode_w = curlx_convert_UTF8_to_wchar(mode);
260
  if(filename_w && mode_w) {
261
    if(fix_excessive_path(filename_w, &fixed))
262
      target = fixed;
263
    else
264
      target = filename_w;
265
    result = _wfopen(target, mode_w);
266
  }
267
  else
268
    /* !checksrc! disable ERRNOVAR 1 */
269
    CURL_SETERRNO(EINVAL);
270
  curlx_unicodefree(filename_w);
271
  curlx_unicodefree(mode_w);
272
#else
273
  if(fix_excessive_path(filename, &fixed))
274
    target = fixed;
275
  else
276
    target = filename;
277
  /* !checksrc! disable BANNEDFUNC 1 */
278
  result = fopen(target, mode);
279
#endif
280
281
  (free)(fixed);
282
  return result;
283
}
284
285
int curlx_win32_stat(const char *path, struct_stat *buffer)
286
{
287
  int result = -1;
288
  TCHAR *fixed = NULL;
289
  const TCHAR *target = NULL;
290
291
#ifdef _UNICODE
292
  wchar_t *path_w = curlx_convert_UTF8_to_wchar(path);
293
  if(path_w) {
294
    if(fix_excessive_path(path_w, &fixed))
295
      target = fixed;
296
    else
297
      target = path_w;
298
#ifndef USE_WIN32_LARGE_FILES
299
    result = _wstat(target, buffer);
300
#else
301
    result = _wstati64(target, buffer);
302
#endif
303
    curlx_unicodefree(path_w);
304
  }
305
  else
306
    /* !checksrc! disable ERRNOVAR 1 */
307
    CURL_SETERRNO(EINVAL);
308
#else
309
  if(fix_excessive_path(path, &fixed))
310
    target = fixed;
311
  else
312
    target = path;
313
#ifndef USE_WIN32_LARGE_FILES
314
  result = _stat(target, buffer);
315
#else
316
  result = _stati64(target, buffer);
317
#endif
318
#endif
319
320
  (free)(fixed);
321
  return result;
322
}
323
324
#endif /* _WIN32 && !UNDER_CE */