@@ -40,6 +40,186 @@ static inline int utf8towchar(const char *filename_utf8, wchar_t **filename_w)
4040 MultiByteToWideChar (CP_UTF8 , 0 , filename_utf8 , -1 , * filename_w , num_chars );
4141 return 0 ;
4242}
43+
44+ /**
45+ * Checks for extended path prefixes for which normalization needs to be skipped.
46+ * see .NET6: PathInternal.IsExtended()
47+ * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L165
48+ */
49+ static inline int path_is_extended (const wchar_t * path )
50+ {
51+ if (path [0 ] == L'\\' && (path [1 ] == L'\\' || path [1 ] == L'?' ) && path [2 ] == L'?' && path [3 ] == L'\\' )
52+ return 1 ;
53+
54+ return 0 ;
55+ }
56+
57+ /**
58+ * Checks for a device path prefix.
59+ * see .NET6: PathInternal.IsDevice()
60+ * we don't check forward slashes and extended paths (as already done)
61+ * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L132
62+ */
63+ static inline int path_is_device_path (const wchar_t * path )
64+ {
65+ if (path [0 ] == L'\\' && path [1 ] == L'\\' && path [2 ] == L'.' && path [3 ] == L'\\' )
66+ return 1 ;
67+
68+ return 0 ;
69+ }
70+
71+ /**
72+ * Performs path normalization by calling GetFullPathNameW().
73+ * see .NET6: PathHelper.GetFullPathName()
74+ * https://github.com/dotnet/runtime/blob/2a99e18eedabcf1add064c099da59d9301ce45e0/src/libraries/System.Private.CoreLib/src/System/IO/PathHelper.Windows.cs#L70
75+ */
76+ static inline int get_full_path_name (wchar_t * * ppath_w )
77+ {
78+ int num_chars ;
79+ wchar_t * temp_w ;
80+
81+ num_chars = GetFullPathNameW (* ppath_w , 0 , NULL , NULL );
82+ if (num_chars <= 0 ) {
83+ errno = EINVAL ;
84+ return -1 ;
85+ }
86+
87+ temp_w = (wchar_t * )av_calloc (num_chars , sizeof (wchar_t ));
88+ if (!temp_w ) {
89+ errno = ENOMEM ;
90+ return -1 ;
91+ }
92+
93+ num_chars = GetFullPathNameW (* ppath_w , num_chars , temp_w , NULL );
94+ if (num_chars <= 0 ) {
95+ av_free (temp_w );
96+ errno = EINVAL ;
97+ return -1 ;
98+ }
99+
100+ av_freep (ppath_w );
101+ * ppath_w = temp_w ;
102+
103+ return 0 ;
104+ }
105+
106+ /**
107+ * Normalizes a Windows file or folder path.
108+ * Expansion of short paths (with 8.3 path components) is currently omitted
109+ * as it is not required for accessing long paths.
110+ * see .NET6: PathHelper.Normalize()
111+ * https://github.com/dotnet/runtime/blob/2a99e18eedabcf1add064c099da59d9301ce45e0/src/libraries/System.Private.CoreLib/src/System/IO/PathHelper.Windows.cs#L25
112+ */
113+ static inline int path_normalize (wchar_t * * ppath_w )
114+ {
115+ int ret ;
116+
117+ if ((ret = get_full_path_name (ppath_w )) < 0 )
118+ return ret ;
119+
120+ /* What .NET does at this point is to call PathHelper.TryExpandShortFileName()
121+ * in case the path contains a '~' character.
122+ * We don't need to do this as we don't need to normalize the file name
123+ * for presentation, and the extended path prefix works with 8.3 path
124+ * components as well
125+ */
126+ return 0 ;
127+ }
128+
129+ /**
130+ * Adds an extended path or UNC prefix to longs paths or paths ending
131+ * with a space or a dot. (' ' or '.').
132+ * This function expects that the path has been normalized before by
133+ * calling path_normalize() and it doesn't check whether the path is
134+ * actually long (> MAX_PATH).
135+ * see .NET6: PathInternal.EnsureExtendedPrefix()
136+ * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L107
137+ */
138+ static inline int add_extended_prefix (wchar_t * * ppath_w )
139+ {
140+ const wchar_t * unc_prefix = L"\\\\?\\UNC\\" ;
141+ const wchar_t * extended_path_prefix = L"\\\\?\\" ;
142+ const wchar_t * path_w = * ppath_w ;
143+ const size_t len = wcslen (path_w );
144+ wchar_t * temp_w ;
145+
146+ /* We're skipping the check IsPartiallyQualified() because
147+ * we expect to have called GetFullPathNameW() already. */
148+ if (len < 2 || path_is_extended (* ppath_w ) || path_is_device_path (* ppath_w )) {
149+ return 0 ;
150+ }
151+
152+ if (path_w [0 ] == L'\\' && path_w [1 ] == L'\\' ) {
153+ /* unc_prefix length is 8 plus 1 for terminating zeros,
154+ * we subtract 2 for the leading '\\' of the original path */
155+ temp_w = (wchar_t * )av_calloc (len - 2 + 8 + 1 , sizeof (wchar_t ));
156+ if (!temp_w ) {
157+ errno = ENOMEM ;
158+ return -1 ;
159+ }
160+ wcscpy (temp_w , unc_prefix );
161+ wcscat (temp_w , path_w + 2 );
162+ } else {
163+ // The length of extended_path_prefix is 4 plus 1 for terminating zeros
164+ temp_w = (wchar_t * )av_calloc (len + 4 + 1 , sizeof (wchar_t ));
165+ if (!temp_w ) {
166+ errno = ENOMEM ;
167+ return -1 ;
168+ }
169+ wcscpy (temp_w , extended_path_prefix );
170+ wcscat (temp_w , path_w );
171+ }
172+
173+ av_freep (ppath_w );
174+ * ppath_w = temp_w ;
175+
176+ return 0 ;
177+ }
178+
179+ /**
180+ * Converts a file or folder path to wchar_t for use with Windows file
181+ * APIs. Paths with extended path prefix (either '\\?\' or \??\') are
182+ * left unchanged.
183+ * All other paths are normalized and converted to absolute paths.
184+ * Longs paths (>= MAX_PATH) are prefixed with the extended path or extended
185+ * UNC path prefix.
186+ * see .NET6: Path.GetFullPath() and Path.GetFullPathInternal()
187+ * https://github.com/dotnet/runtime/blob/2a99e18eedabcf1add064c099da59d9301ce45e0/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs#L126
188+ */
189+ static inline int get_extended_win32_path (const char * path , wchar_t * * ppath_w )
190+ {
191+ int ret ;
192+ size_t len ;
193+
194+ if ((ret = utf8towchar (path , ppath_w )) < 0 )
195+ return ret ;
196+
197+ if (path_is_extended (* ppath_w )) {
198+ /* Paths prefixed with '\\?\' or \??\' are considered normalized by definition.
199+ * Windows doesn't normalize those paths and neither should we.
200+ */
201+ return 0 ;
202+ }
203+
204+ if ((ret = path_normalize (ppath_w )) < 0 ) {
205+ av_freep (ppath_w );
206+ return ret ;
207+ }
208+
209+ /* see .NET6: PathInternal.EnsureExtendedPrefixIfNeeded()
210+ * https://github.com/dotnet/runtime/blob/9260c249140ef90b4299d0fe1aa3037e25228518/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L92
211+ */
212+ len = wcslen (* ppath_w );
213+ if (len >= MAX_PATH ) {
214+ if ((ret = add_extended_prefix (ppath_w )) < 0 ) {
215+ av_freep (ppath_w );
216+ return ret ;
217+ }
218+ }
219+
220+ return 0 ;
221+ }
222+
43223#endif
44224
45225#endif /* AVUTIL_WCHAR_FILENAME_H */
0 commit comments