fopen.c 8.9 KB

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