1/* 2 * This file is part of FFmpeg. 3 * 4 * FFmpeg is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * FFmpeg is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with FFmpeg; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 19#ifndef AVUTIL_WCHAR_FILENAME_H 20#define AVUTIL_WCHAR_FILENAME_H 21 22#ifdef _WIN32 23 24#define WIN32_LEAN_AND_MEAN 25#include <windows.h> 26#include "mem.h" 27 28av_warn_unused_result 29static inline int utf8towchar(const char *filename_utf8, wchar_t **filename_w) 30{ 31 int num_chars; 32 num_chars = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename_utf8, -1, NULL, 0); 33 if (num_chars <= 0) { 34 *filename_w = NULL; 35 errno = EINVAL; 36 return -1; 37 } 38 *filename_w = (wchar_t *)av_calloc(num_chars, sizeof(wchar_t)); 39 if (!*filename_w) { 40 errno = ENOMEM; 41 return -1; 42 } 43 MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, *filename_w, num_chars); 44 return 0; 45} 46 47av_warn_unused_result 48static inline int wchartocp(unsigned int code_page, const wchar_t *filename_w, 49 char **filename) 50{ 51 DWORD flags = code_page == CP_UTF8 ? WC_ERR_INVALID_CHARS : 0; 52 int num_chars = WideCharToMultiByte(code_page, flags, filename_w, -1, 53 NULL, 0, NULL, NULL); 54 if (num_chars <= 0) { 55 *filename = NULL; 56 errno = EINVAL; 57 return -1; 58 } 59 *filename = (char*)av_malloc_array(num_chars, sizeof *filename); 60 if (!*filename) { 61 errno = ENOMEM; 62 return -1; 63 } 64 WideCharToMultiByte(code_page, flags, filename_w, -1, 65 *filename, num_chars, NULL, NULL); 66 return 0; 67} 68 69av_warn_unused_result 70static inline int wchartoutf8(const wchar_t *filename_w, char **filename) 71{ 72 return wchartocp(CP_UTF8, filename_w, filename); 73} 74 75av_warn_unused_result 76static inline int wchartoansi(const wchar_t *filename_w, char **filename) 77{ 78 return wchartocp(CP_ACP, filename_w, filename); 79} 80 81av_warn_unused_result 82static inline int utf8toansi(const char *filename_utf8, char **filename) 83{ 84 wchar_t *filename_w = NULL; 85 int ret = -1; 86 if (utf8towchar(filename_utf8, &filename_w)) 87 return -1; 88 89 if (!filename_w) { 90 *filename = NULL; 91 return 0; 92 } 93 94 ret = wchartoansi(filename_w, filename); 95 av_free(filename_w); 96 return ret; 97} 98 99/** 100 * Checks for extended path prefixes for which normalization needs to be skipped. 101 * see .NET6: PathInternal.IsExtended() 102 * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L165 103 */ 104static inline int path_is_extended(const wchar_t *path) 105{ 106 if (path[0] == L'\\' && (path[1] == L'\\' || path[1] == L'?') && path[2] == L'?' && path[3] == L'\\') 107 return 1; 108 109 return 0; 110} 111 112/** 113 * Checks for a device path prefix. 114 * see .NET6: PathInternal.IsDevice() 115 * we don't check forward slashes and extended paths (as already done) 116 * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L132 117 */ 118static inline int path_is_device_path(const wchar_t *path) 119{ 120 if (path[0] == L'\\' && path[1] == L'\\' && path[2] == L'.' && path[3] == L'\\') 121 return 1; 122 123 return 0; 124} 125 126/** 127 * Performs path normalization by calling GetFullPathNameW(). 128 * see .NET6: PathHelper.GetFullPathName() 129 * https://github.com/dotnet/runtime/blob/2a99e18eedabcf1add064c099da59d9301ce45e0/src/libraries/System.Private.CoreLib/src/System/IO/PathHelper.Windows.cs#L70 130 */ 131static inline int get_full_path_name(wchar_t **ppath_w) 132{ 133 int num_chars; 134 wchar_t *temp_w; 135 136 num_chars = GetFullPathNameW(*ppath_w, 0, NULL, NULL); 137 if (num_chars <= 0) { 138 errno = EINVAL; 139 return -1; 140 } 141 142 temp_w = (wchar_t *)av_calloc(num_chars, sizeof(wchar_t)); 143 if (!temp_w) { 144 errno = ENOMEM; 145 return -1; 146 } 147 148 num_chars = GetFullPathNameW(*ppath_w, num_chars, temp_w, NULL); 149 if (num_chars <= 0) { 150 av_free(temp_w); 151 errno = EINVAL; 152 return -1; 153 } 154 155 av_freep(ppath_w); 156 *ppath_w = temp_w; 157 158 return 0; 159} 160 161/** 162 * Normalizes a Windows file or folder path. 163 * Expansion of short paths (with 8.3 path components) is currently omitted 164 * as it is not required for accessing long paths. 165 * see .NET6: PathHelper.Normalize() 166 * https://github.com/dotnet/runtime/blob/2a99e18eedabcf1add064c099da59d9301ce45e0/src/libraries/System.Private.CoreLib/src/System/IO/PathHelper.Windows.cs#L25 167 */ 168static inline int path_normalize(wchar_t **ppath_w) 169{ 170 int ret; 171 172 if ((ret = get_full_path_name(ppath_w)) < 0) 173 return ret; 174 175 /* What .NET does at this point is to call PathHelper.TryExpandShortFileName() 176 * in case the path contains a '~' character. 177 * We don't need to do this as we don't need to normalize the file name 178 * for presentation, and the extended path prefix works with 8.3 path 179 * components as well 180 */ 181 return 0; 182} 183 184/** 185 * Adds an extended path or UNC prefix to longs paths or paths ending 186 * with a space or a dot. (' ' or '.'). 187 * This function expects that the path has been normalized before by 188 * calling path_normalize() and it doesn't check whether the path is 189 * actually long (> MAX_PATH). 190 * see .NET6: PathInternal.EnsureExtendedPrefix() 191 * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L107 192 */ 193static inline int add_extended_prefix(wchar_t **ppath_w) 194{ 195 const wchar_t *unc_prefix = L"\\\\?\\UNC\\"; 196 const wchar_t *extended_path_prefix = L"\\\\?\\"; 197 const wchar_t *path_w = *ppath_w; 198 const size_t len = wcslen(path_w); 199 wchar_t *temp_w; 200 201 /* We're skipping the check IsPartiallyQualified() because 202 * we expect to have called GetFullPathNameW() already. */ 203 if (len < 2 || path_is_extended(*ppath_w) || path_is_device_path(*ppath_w)) { 204 return 0; 205 } 206 207 if (path_w[0] == L'\\' && path_w[1] == L'\\') { 208 /* unc_prefix length is 8 plus 1 for terminating zeros, 209 * we subtract 2 for the leading '\\' of the original path */ 210 temp_w = (wchar_t *)av_calloc(len - 2 + 8 + 1, sizeof(wchar_t)); 211 if (!temp_w) { 212 errno = ENOMEM; 213 return -1; 214 } 215 wcscpy(temp_w, unc_prefix); 216 wcscat(temp_w, path_w + 2); 217 } else { 218 // The length of extended_path_prefix is 4 plus 1 for terminating zeros 219 temp_w = (wchar_t *)av_calloc(len + 4 + 1, sizeof(wchar_t)); 220 if (!temp_w) { 221 errno = ENOMEM; 222 return -1; 223 } 224 wcscpy(temp_w, extended_path_prefix); 225 wcscat(temp_w, path_w); 226 } 227 228 av_freep(ppath_w); 229 *ppath_w = temp_w; 230 231 return 0; 232} 233 234/** 235 * Converts a file or folder path to wchar_t for use with Windows file 236 * APIs. Paths with extended path prefix (either '\\?\' or \??\') are 237 * left unchanged. 238 * All other paths are normalized and converted to absolute paths. 239 * Longs paths (>= MAX_PATH) are prefixed with the extended path or extended 240 * UNC path prefix. 241 * see .NET6: Path.GetFullPath() and Path.GetFullPathInternal() 242 * https://github.com/dotnet/runtime/blob/2a99e18eedabcf1add064c099da59d9301ce45e0/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs#L126 243 */ 244static inline int get_extended_win32_path(const char *path, wchar_t **ppath_w) 245{ 246 int ret; 247 size_t len; 248 249 if ((ret = utf8towchar(path, ppath_w)) < 0) 250 return ret; 251 252 if (path_is_extended(*ppath_w)) { 253 /* Paths prefixed with '\\?\' or \??\' are considered normalized by definition. 254 * Windows doesn't normalize those paths and neither should we. 255 */ 256 return 0; 257 } 258 259 if ((ret = path_normalize(ppath_w)) < 0) { 260 av_freep(ppath_w); 261 return ret; 262 } 263 264 /* see .NET6: PathInternal.EnsureExtendedPrefixIfNeeded() 265 * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L92 266 */ 267 len = wcslen(*ppath_w); 268 if (len >= MAX_PATH) { 269 if ((ret = add_extended_prefix(ppath_w)) < 0) { 270 av_freep(ppath_w); 271 return ret; 272 } 273 } 274 275 return 0; 276} 277 278#endif 279 280#endif /* AVUTIL_WCHAR_FILENAME_H */ 281