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