1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Text;
7 
8 namespace System.IO
9 {
10     public static partial class Path
11     {
GetInvalidFileNameChars()12         public static char[] GetInvalidFileNameChars() => new char[]
13         {
14             '\"', '<', '>', '|', '\0',
15             (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
16             (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
17             (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
18             (char)31, ':', '*', '?', '\\', '/'
19         };
20 
GetInvalidPathChars()21         public static char[] GetInvalidPathChars() => new char[]
22         {
23             '|', '\0',
24             (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
25             (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
26             (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
27             (char)31
28         };
29 
30         // The max total path is 260, and the max individual component length is 255.
31         // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars.
32         internal const int MaxPath = 260;
33 
34         // Expands the given path to a fully qualified path.
GetFullPath(string path)35         public static string GetFullPath(string path)
36         {
37             if (path == null)
38                 throw new ArgumentNullException(nameof(path));
39 
40             // Embedded null characters are the only invalid character case we want to check up front.
41             // This is because the nulls will signal the end of the string to Win32 and therefore have
42             // unpredictable results. Other invalid characters we give a chance to be normalized out.
43             if (path.IndexOf('\0') != -1)
44                 throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
45 
46             if (PathInternal.IsExtended(path))
47             {
48                 // We can't really know what is valid for all cases of extended paths.
49                 //
50                 //  - object names can include other characters as well (':', '/', etc.)
51                 //  - even file objects have different rules (pipe names can contain most characters)
52                 //
53                 // As such we will do no further analysis of extended paths to avoid blocking known and unknown
54                 // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
55                 return path;
56             }
57 
58             bool isDevice = PathInternal.IsDevice(path);
59             if (!isDevice)
60             {
61                 // Toss out paths with colons that aren't a valid drive specifier.
62                 // Cannot start with a colon and can only be of the form "C:".
63                 // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
64                 int startIndex = PathInternal.PathStartSkip(path);
65 
66                 // Move past the colon
67                 startIndex += 2;
68 
69                 if ((path.Length > 0 && path[0] == PathInternal.VolumeSeparatorChar)
70                     || (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2]))
71                     || (path.Length > startIndex && path.IndexOf(PathInternal.VolumeSeparatorChar, startIndex) != -1))
72                 {
73                     throw new NotSupportedException(SR.Format(SR.Argument_PathFormatNotSupported_Path, path));
74                 }
75             }
76 
77             // Technically this doesn't matter but we used to throw for this case
78             if (PathInternal.IsEffectivelyEmpty(path))
79                 throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
80 
81             // We don't want to check invalid characters for device format- see comments for extended above
82             string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true);
83 
84             if (!isDevice)
85             {
86                 // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked)
87                 if (PathInternal.HasWildCardCharacters(fullPath))
88                     throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
89             }
90 
91             return fullPath;
92         }
93 
GetTempPath()94         public static string GetTempPath()
95         {
96             StringBuilder sb = StringBuilderCache.Acquire(MaxPath);
97             uint r = Interop.Kernel32.GetTempPathW(MaxPath, sb);
98             if (r == 0)
99                 throw Win32Marshal.GetExceptionForLastWin32Error();
100             return GetFullPath(StringBuilderCache.GetStringAndRelease(sb));
101         }
102 
103         // Returns a unique temporary file name, and creates a 0-byte file by that
104         // name on disk.
GetTempFileName()105         public static string GetTempFileName()
106         {
107             string path = GetTempPath();
108 
109             StringBuilder sb = StringBuilderCache.Acquire(MaxPath);
110             uint r = Interop.Kernel32.GetTempFileNameW(path, "tmp", 0, sb);
111             if (r == 0)
112                 throw Win32Marshal.GetExceptionForLastWin32Error();
113             return StringBuilderCache.GetStringAndRelease(sb);
114         }
115 
116         // Tests if the given path contains a root. A path is considered rooted
117         // if it starts with a backslash ("\") or a valid drive letter and a colon (":").
IsPathRooted(string path)118         public static bool IsPathRooted(string path)
119         {
120             if (path != null)
121             {
122                 int length = path.Length;
123                 if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) ||
124                     (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar))
125                     return true;
126             }
127             return false;
128         }
129 
130         // Returns the root portion of the given path. The resulting string
131         // consists of those rightmost characters of the path that constitute the
132         // root of the path. Possible patterns for the resulting string are: An
133         // empty string (a relative path on the current drive), "\" (an absolute
134         // path on the current drive), "X:" (a relative path on a given drive,
135         // where X is the drive letter), "X:\" (an absolute path on a given drive),
136         // and "\\server\share" (a UNC path for a given server and share name).
137         // The resulting string is null if path is null. If the path is empty or
138         // only contains whitespace characters an ArgumentException gets thrown.
GetPathRoot(string path)139         public static string GetPathRoot(string path)
140         {
141             if (path == null) return null;
142             if (PathInternal.IsEffectivelyEmpty(path))
143                 throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
144 
145             // Need to return the normalized directory separator
146             path = PathInternal.NormalizeDirectorySeparators(path);
147 
148             int pathRoot = PathInternal.GetRootLength(path);
149             return pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot);
150         }
151 
152         /// <summary>Gets whether the system is case-sensitive.</summary>
153         internal static bool IsCaseSensitive { get { return false; } }
154     }
155 }
156