1 /*
2  * mptPathString.h
3  * ---------------
4  * Purpose: Wrapper class around the platform-native representation of path names. Should be the only type that is used to store path names.
5  * Notes  : Currently none.
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 
11 #pragma once
12 
13 #include "openmpt/all/BuildSettings.hpp"
14 
15 #include "mptString.h"
16 
17 #include "mpt/base/namespace.hpp"
18 
19 #include <vector>
20 
21 #include "openmpt/base/FlagSet.hpp"
22 
23 #define MPT_DEPRECATED_PATH
24 //#define MPT_DEPRECATED_PATH [[deprecated]]
25 
26 OPENMPT_NAMESPACE_BEGIN
27 
28 namespace mpt
29 {
30 
31 #if MPT_OS_WINDOWS
32 typedef mpt::winstring RawPathString;
33 #else // !MPT_OS_WINDOWS
34 typedef std::string RawPathString;
35 #endif // if MPT_OS_WINDOWS
36 
37 
38 
39 class PathString
40 {
41 
42 private:
43 
44 	RawPathString path;
45 
46 private:
47 
PathString(const RawPathString & path_)48 	explicit PathString(const RawPathString & path_)
49 		: path(path_)
50 	{
51 		return;
52 	}
53 
54 public:
55 
PathString()56 	PathString()
57 	{
58 		return;
59 	}
PathString(const PathString & other)60 	PathString(const PathString & other)
61 		: path(other.path)
62 	{
63 		return;
64 	}
PathString(PathString && other)65 	PathString(PathString && other) noexcept
66 		: path(std::move(other.path))
67 	{
68 		return;
69 	}
assign(const PathString & other)70 	PathString & assign(const PathString & other)
71 	{
72 		path = other.path;
73 		return *this;
74 	}
assign(PathString && other)75 	PathString & assign(PathString && other) noexcept
76 	{
77 		path = std::move(other.path);
78 		return *this;
79 	}
80 	PathString & operator = (const PathString & other)
81 	{
82 		return assign(other);
83 	}
84 	PathString &operator = (PathString && other) noexcept
85 	{
86 		return assign(std::move(other));
87 	}
append(const PathString & other)88 	PathString & append(const PathString & other)
89 	{
90 		path.append(other.path);
91 		return *this;
92 	}
93 	PathString & operator += (const PathString & other)
94 	{
95 		return append(other);
96 	}
97 
98 	friend PathString operator + (const PathString & a, const PathString & b)
99 	{
100 		return PathString(a).append(b);
101 	}
102 
103 	friend bool operator < (const PathString & a, const PathString & b)
104 	{
105 		return a.AsNative() < b.AsNative();
106 	}
107 	friend bool operator == (const PathString & a, const PathString & b)
108 	{
109 		return a.AsNative() == b.AsNative();
110 	}
111 	friend bool operator != (const PathString & a, const PathString & b)
112 	{
113 		return a.AsNative() != b.AsNative();
114 	}
115 
empty()116 	bool empty() const { return path.empty(); }
117 
Length()118 	std::size_t Length() const { return path.size(); }
119 
120 
121 
122 public:
123 
124 #if MPT_OS_WINDOWS
125 #if !MPT_OS_WINDOWS_WINRT
126 	static int CompareNoCase(const PathString & a, const PathString & b);
127 #endif // !MPT_OS_WINDOWS_WINRT
128 #endif
129 
130 #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
131 
132 	void SplitPath(PathString *drive, PathString *dir, PathString *fname, PathString *ext) const;
133 	// \\?\ prefixes will be removed and \\?\\UNC prefixes converted to canonical \\ form.
134 	PathString GetDrive() const;		// Drive letter + colon, e.g. "C:" or \\server\\share
135 	PathString GetDir() const;			// Directory, e.g. "\OpenMPT\"
136 	PathString GetPath() const;			// Drive + Dir, e.g. "C:\OpenMPT\"
137 	PathString GetFileName() const;		// File name without extension, e.g. "OpenMPT"
138 	PathString GetFileExt() const;		// Extension including dot, e.g. ".exe"
139 	PathString GetFullFileName() const;	// File name + extension, e.g. "OpenMPT.exe"
140 
141 	// Verify if this path represents a valid directory on the file system.
142 	bool IsDirectory() const;
143 	// Verify if this path exists and is a file on the file system.
144 	bool IsFile() const;
145 
146 	bool FileOrDirectoryExists() const;
147 
148 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
149 
150 	static bool IsPathSeparator(RawPathString::value_type c);
151 	static RawPathString::value_type GetDefaultPathSeparator();
152 
153 #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
154 
155 	// Return the same path string with a different (or appended) extension (including "."), e.g. "foo.bar",".txt" -> "foo.txt" or "C:\OpenMPT\foo",".txt" -> "C:\OpenMPT\foo.txt"
156 	PathString ReplaceExt(const mpt::PathString &newExt) const;
157 
158 	// Removes special characters from a filename component and replaces them with a safe replacement character ("_" on windows).
159 	// Returns the result.
160 	// Note that this also removes path component separators, so this should only be used on single-component PathString objects.
161 	PathString SanitizeComponent() const;
162 
HasTrailingSlash()163 	bool HasTrailingSlash() const
164 	{
165 		if(path.empty())
166 		{
167 			return false;
168 		}
169 		RawPathString::value_type c = path[path.length() - 1];
170 		return IsPathSeparator(c);
171 	}
EnsureTrailingSlash()172 	mpt::PathString &EnsureTrailingSlash()
173 	{
174 		if(!path.empty() && !HasTrailingSlash())
175 		{
176 			path += GetDefaultPathSeparator();
177 		}
178 		return *this;
179 	}
180 
WithoutTrailingSlash()181 	mpt::PathString WithoutTrailingSlash() const
182 	{
183 		mpt::PathString result = *this;
184 		while(result.HasTrailingSlash())
185 		{
186 			if(result.Length() == 1)
187 			{
188 				return result;
189 			}
190 			result = mpt::PathString(result.AsNative().substr(0, result.AsNative().length() - 1));
191 		}
192 		return result;
193 	}
194 
WithTrailingSlash()195 	mpt::PathString WithTrailingSlash() const
196 	{
197 		mpt::PathString result = *this;
198 		result.EnsureTrailingSlash();
199 		return result;
200 	}
201 
202 	// Relative / absolute paths conversion
203 	mpt::PathString AbsolutePathToRelative(const mpt::PathString &relativeTo) const;
204 	mpt::PathString RelativePathToAbsolute(const mpt::PathString &relativeTo) const;
205 
206 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
207 
208 public:
209 
210 #if MPT_OS_WINDOWS
211 
212 #if !(MPT_WSTRING_CONVERT)
213 #error "mpt::PathString on Windows depends on MPT_WSTRING_CONVERT)"
214 #endif
215 	// conversions
216 #if defined(MPT_ENABLE_CHARSET_LOCALE)
ToLocale()217 	MPT_DEPRECATED_PATH std::string ToLocale() const { return mpt::ToCharset(mpt::Charset::Locale, path); }
218 #endif
ToUTF8()219 	std::string ToUTF8() const { return mpt::ToCharset(mpt::Charset::UTF8, path); }
ToWide()220 	std::wstring ToWide() const { return mpt::ToWide(path); }
ToUnicode()221 	mpt::ustring ToUnicode() const { return mpt::ToUnicode(path); }
222 #if defined(MPT_ENABLE_CHARSET_LOCALE)
FromLocale(const std::string & path)223 	MPT_DEPRECATED_PATH static PathString FromLocale(const std::string &path) { return PathString(mpt::ToWin(mpt::Charset::Locale, path)); }
FromLocaleSilent(const std::string & path)224 	static PathString FromLocaleSilent(const std::string &path) { return PathString(mpt::ToWin(mpt::Charset::Locale, path)); }
225 #endif
FromUTF8(const std::string & path)226 	static PathString FromUTF8(const std::string &path) { return PathString(mpt::ToWin(mpt::Charset::UTF8, path)); }
FromWide(const std::wstring & path)227 	static PathString FromWide(const std::wstring &path) { return PathString(mpt::ToWin(path)); }
FromUnicode(const mpt::ustring & path)228 	static PathString FromUnicode(const mpt::ustring &path) { return PathString(mpt::ToWin(path)); }
AsNative()229 	RawPathString AsNative() const { return path; }
230 	// Return native string, with possible \\?\ prefix if it exceeds MAX_PATH characters.
231 	RawPathString AsNativePrefixed() const;
FromNative(const RawPathString & path)232 	static PathString FromNative(const RawPathString &path) { return PathString(path); }
233 #if defined(MPT_WITH_MFC)
234 	// CString TCHAR, so this is CHAR or WCHAR, depending on UNICODE
ToCString()235 	CString ToCString() const { return mpt::ToCString(path); }
FromCString(const CString & path)236 	static PathString FromCString(const CString &path) { return PathString(mpt::ToWin(path)); }
237 #endif // MPT_WITH_MFC
238 
239 	// Convert a path to its simplified form, i.e. remove ".\" and "..\" entries
240 	mpt::PathString Simplify() const;
241 
242 #else // !MPT_OS_WINDOWS
243 
244 	// conversions
245 #if defined(MPT_ENABLE_CHARSET_LOCALE)
246 	std::string ToLocale() const { return path; }
247 	std::string ToUTF8() const { return mpt::ToCharset(mpt::Charset::UTF8, mpt::Charset::Locale, path); }
248 #if MPT_WSTRING_CONVERT
249 	std::wstring ToWide() const { return mpt::ToWide(mpt::Charset::Locale, path); }
250 #endif
251 	mpt::ustring ToUnicode() const { return mpt::ToUnicode(mpt::Charset::Locale, path); }
252 	static PathString FromLocale(const std::string &path) { return PathString(path); }
253 	static PathString FromLocaleSilent(const std::string &path) { return PathString(path); }
254 	static PathString FromUTF8(const std::string &path) { return PathString(mpt::ToCharset(mpt::Charset::Locale, mpt::Charset::UTF8, path)); }
255 #if MPT_WSTRING_CONVERT
256 	static PathString FromWide(const std::wstring &path) { return PathString(mpt::ToCharset(mpt::Charset::Locale, path)); }
257 #endif
258 	static PathString FromUnicode(const mpt::ustring &path) { return PathString(mpt::ToCharset(mpt::Charset::Locale, path)); }
259 	RawPathString AsNative() const { return path; }
260 	RawPathString AsNativePrefixed() const { return path; }
261 	static PathString FromNative(const RawPathString &path) { return PathString(path); }
262 #else // !MPT_ENABLE_CHARSET_LOCALE
263 	std::string ToUTF8() const { return path; }
264 #if MPT_WSTRING_CONVERT
265 	std::wstring ToWide() const { return mpt::ToWide(mpt::Charset::UTF8, path); }
266 #endif
267 	mpt::ustring ToUnicode() const { return mpt::ToUnicode(mpt::Charset::UTF8, path); }
268 	static PathString FromUTF8(const std::string &path) { return PathString(path); }
269 #if MPT_WSTRING_CONVERT
270 	static PathString FromWide(const std::wstring &path) { return PathString(mpt::ToCharset(mpt::Charset::UTF8, path)); }
271 #endif
272 	static PathString FromUnicode(const mpt::ustring &path) { return PathString(mpt::ToCharset(mpt::Charset::UTF8, path)); }
273 	RawPathString AsNative() const { return path; }
274 	RawPathString AsNativePrefixed() const { return path; }
275 	static PathString FromNative(const RawPathString &path) { return PathString(path); }
276 #endif // MPT_ENABLE_CHARSET_LOCALE
277 
278 	// Convert a path to its simplified form (currently only implemented on Windows)
279 	[[deprecated]] mpt::PathString Simplify() const { return PathString(path); }
280 
281 #endif // MPT_OS_WINDOWS
282 
283 };
284 
285 
286 
287 #if defined(MPT_ENABLE_CHARSET_LOCALE)
288 #if MPT_OS_WINDOWS
289 #ifdef UNICODE
ToAString(const mpt::PathString & x)290 [[deprecated]] inline std::string ToAString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.ToUnicode()); }
291 #else
ToAString(const mpt::PathString & x)292 MPT_DEPRECATED_PATH inline std::string ToAString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.AsNative()); }
293 #endif
294 #else
ToAString(const mpt::PathString & x)295 MPT_DEPRECATED_PATH inline std::string ToAString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.ToUnicode()); }
296 #endif
297 #endif
ToUString(const mpt::PathString & x)298 inline mpt::ustring ToUString(const mpt::PathString & x) { return x.ToUnicode(); }
299 #if MPT_WSTRING_FORMAT
ToWString(const mpt::PathString & x)300 inline std::wstring ToWString(const mpt::PathString & x) { return x.ToWide(); }
301 #endif
302 
303 } // namespace mpt
304 
305 #if MPT_OS_WINDOWS
306 
307 #ifdef UNICODE
308 #define MPT_PATHSTRING_LITERAL(x) ( L ## x )
309 #define MPT_PATHSTRING(x) mpt::PathString::FromNative( L ## x )
310 #else
311 #define MPT_PATHSTRING_LITERAL(x) ( x )
312 #define MPT_PATHSTRING(x) mpt::PathString::FromNative( x )
313 #endif
314 
315 #else // !MPT_OS_WINDOWS
316 
317 #define MPT_PATHSTRING_LITERAL(x) ( x )
318 #define MPT_PATHSTRING(x) mpt::PathString::FromNative( x )
319 
320 #endif // MPT_OS_WINDOWS
321 
322 #define PC_(x) MPT_PATHSTRING_LITERAL(x)
323 #define PL_(x) MPT_PATHSTRING_LITERAL(x)
324 #define P_(x) MPT_PATHSTRING(x)
325 
326 namespace mpt
327 {
328 
329 
330 bool PathIsAbsolute(const mpt::PathString &path);
331 
332 #if MPT_OS_WINDOWS
333 
334 #if !(MPT_OS_WINDOWS_WINRT && (_WIN32_WINNT < 0x0a00))
335 
336 // Returns the absolute path for a potentially relative path and removes ".." or "." components. (same as GetFullPathNameW)
337 mpt::PathString GetAbsolutePath(const mpt::PathString &path);
338 
339 #endif
340 
341 #ifdef MODPLUG_TRACKER
342 
343 // Deletes a complete directory tree. Handle with EXTREME care.
344 // Returns false if any file could not be removed and aborts as soon as it
345 // encounters any error. path must be absolute.
346 bool DeleteWholeDirectoryTree(mpt::PathString path);
347 
348 #endif // MODPLUG_TRACKER
349 
350 #endif // MPT_OS_WINDOWS
351 
352 #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
353 
354 // Returns the application executable path or an empty string (if unknown), e.g. "C:\mptrack\"
355 mpt::PathString GetExecutablePath();
356 
357 #if !MPT_OS_WINDOWS_WINRT
358 // Returns the system directory path, e.g. "C:\Windows\System32\"
359 mpt::PathString GetSystemPath();
360 #endif // !MPT_OS_WINDOWS_WINRT
361 
362 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
363 
364 #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
365 
366 // Returns temporary directory (with trailing backslash added) (e.g. "C:\TEMP\")
367 mpt::PathString GetTempDirectory();
368 
369 // Returns a new unique absolute path.
370 mpt::PathString CreateTempFileName(const mpt::PathString &fileNamePrefix = mpt::PathString(), const mpt::PathString &fileNameExtension = P_("tmp"));
371 
372 
373 
374 // Scoped temporary file guard. Deletes the file when going out of scope.
375 // The file itself is not created automatically.
376 class TempFileGuard
377 {
378 private:
379 	const mpt::PathString filename;
380 public:
381 	TempFileGuard(const mpt::PathString &filename = CreateTempFileName());
382 	mpt::PathString GetFilename() const;
383 	~TempFileGuard();
384 };
385 
386 
387 // Scoped temporary directory guard. Deletes the directory when going out of scope.
388 // The directory itself is created automatically.
389 class TempDirGuard
390 {
391 private:
392 	mpt::PathString dirname;
393 public:
394 	TempDirGuard(const mpt::PathString &dirname_ = CreateTempFileName());
395 	mpt::PathString GetDirname() const;
396 	~TempDirGuard();
397 };
398 
399 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
400 
401 } // namespace mpt
402 
403 
404 
405 #if defined(MODPLUG_TRACKER)
406 
407 // Sanitize a filename (remove special chars)
408 void SanitizeFilename(mpt::PathString &filename);
409 
410 void SanitizeFilename(char *beg, char *end);
411 void SanitizeFilename(wchar_t *beg, wchar_t *end);
412 
413 void SanitizeFilename(std::string &str);
414 void SanitizeFilename(std::wstring &str);
415 #if MPT_USTRING_MODE_UTF8
416 void SanitizeFilename(mpt::u8string &str);
417 #endif // MPT_USTRING_MODE_UTF8
418 
419 template <std::size_t size>
SanitizeFilename(char (& buffer)[size])420 void SanitizeFilename(char (&buffer)[size])
421 {
422 	static_assert(size > 0);
423 	SanitizeFilename(buffer, buffer + size);
424 }
425 
426 template <std::size_t size>
SanitizeFilename(wchar_t (& buffer)[size])427 void SanitizeFilename(wchar_t (&buffer)[size])
428 {
429 	static_assert(size > 0);
430 	SanitizeFilename(buffer, buffer + size);
431 }
432 
433 #if defined(MPT_WITH_MFC)
434 void SanitizeFilename(CString &str);
435 #endif // MPT_WITH_MFC
436 
437 #endif // MODPLUG_TRACKER
438 
439 
440 #if defined(MODPLUG_TRACKER)
441 
442 enum FileTypeFormat
443 {
444 	FileTypeFormatNone           = 0   , // do not show extensions after description, i.e. "Foo Files"
445 	FileTypeFormatShowExtensions = 1<<0, // show extensions after descripten, i.e. "Foo Files (*.foo,*.bar)"
446 };
MPT_DECLARE_ENUM(FileTypeFormat)447 MPT_DECLARE_ENUM(FileTypeFormat)
448 
449 class FileType
450 {
451 private:
452 	mpt::ustring m_ShortName; // "flac", "mod" (lowercase)
453 	mpt::ustring m_Description; // "FastTracker 2 Module"
454 	std::vector<std::string> m_MimeTypes; // "audio/ogg" (in ASCII)
455 	std::vector<mpt::PathString> m_Extensions; // "mod", "xm" (lowercase)
456 	std::vector<mpt::PathString> m_Prefixes; // "mod" for "mod.*"
457 public:
458 	FileType() { }
459 	FileType(const std::vector<FileType> &group)
460 	{
461 		for(const auto &type : group)
462 		{
463 			mpt::append(m_MimeTypes, type.m_MimeTypes);
464 			mpt::append(m_Extensions, type.m_Extensions);
465 			mpt::append(m_Prefixes, type.m_Prefixes);
466 		}
467 	}
468 	static FileType Any()
469 	{
470 		return FileType().ShortName(U_("*")).Description(U_("All Files")).AddExtension(P_("*"));
471 	}
472 public:
473 	FileType& ShortName(const mpt::ustring &shortName) { m_ShortName = shortName; return *this; }
474 	FileType& Description(const mpt::ustring &description) { m_Description = description; return *this; }
475 	FileType& MimeTypes(const std::vector<std::string> &mimeTypes) { m_MimeTypes = mimeTypes; return *this; }
476 	FileType& Extensions(const std::vector<mpt::PathString> &extensions) { m_Extensions = extensions; return *this; }
477 	FileType& Prefixes(const std::vector<mpt::PathString> &prefixes) { m_Prefixes = prefixes; return *this; }
478 	FileType& AddMimeType(const std::string &mimeType) { m_MimeTypes.push_back(mimeType); return *this; }
479 	FileType& AddExtension(const mpt::PathString &extension) { m_Extensions.push_back(extension); return *this; }
480 	FileType& AddPrefix(const mpt::PathString &prefix) { m_Prefixes.push_back(prefix); return *this; }
481 public:
482 	mpt::ustring GetShortName() const { return m_ShortName; }
483 	mpt::ustring GetDescription() const { return m_Description; }
484 	std::vector<std::string> GetMimeTypes() const { return m_MimeTypes; }
485 	std::vector<mpt::PathString> GetExtensions() const { return m_Extensions; }
486 	std::vector<mpt::PathString> GetPrefixes() const { return m_Prefixes; }
487 public:
488 	mpt::PathString AsFilterString(FlagSet<FileTypeFormat> format = FileTypeFormatNone) const;
489 	mpt::PathString AsFilterOnlyString() const;
490 }; // class FileType
491 
492 
493 // "Ogg Vorbis|*.ogg;*.oga|" // FileTypeFormatNone
494 // "Ogg Vorbis (*.ogg,*.oga)|*.ogg;*.oga|" // FileTypeFormatShowExtensions
495 mpt::PathString ToFilterString(const FileType &fileType, FlagSet<FileTypeFormat> format = FileTypeFormatNone);
496 mpt::PathString ToFilterString(const std::vector<FileType> &fileTypes, FlagSet<FileTypeFormat> format = FileTypeFormatNone);
497 
498 // "*.ogg;*.oga" / ";*.ogg;*.oga"
499 mpt::PathString ToFilterOnlyString(const FileType &fileType, bool prependSemicolonWhenNotEmpty = false);
500 mpt::PathString ToFilterOnlyString(const std::vector<FileType> &fileTypes, bool prependSemicolonWhenNotEmpty = false);
501 
502 #endif // MODPLUG_TRACKER
503 
504 
505 OPENMPT_NAMESPACE_END
506