FFmpeg
wchar_filename.h
Go to the documentation of this file.
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 
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 
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 
71 static inline int wchartoutf8(const wchar_t *filename_w, char **filename)
72 {
73  return wchartocp(CP_UTF8, filename_w, filename);
74 }
75 
77 static inline int wchartoansi(const wchar_t *filename_w, char **filename)
78 {
79  return wchartocp(CP_ACP, filename_w, filename);
80 }
81 
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 */
NULL
#define NULL
Definition: coverity.c:32
av_warn_unused_result
#define av_warn_unused_result
Definition: attributes.h:64
av_malloc_array
#define av_malloc_array(a, b)
Definition: tableprint_vlc.h:31
len
int len
Definition: vorbis_enc_data.h:426
av_calloc
void * av_calloc(size_t nmemb, size_t size)
Definition: mem.c:264
ret
ret
Definition: filter_design.txt:187
L
#define L(x)
Definition: vpx_arith.h:36
mem.h
av_free
#define av_free(p)
Definition: tableprint_vlc.h:33
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:34
flags
#define flags(name, subs,...)
Definition: cbs_av1.c:482