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