2014-08-13 10:39:27 +01:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// System.IO.Path.cs
|
|
|
|
//
|
|
|
|
// Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
|
|
|
|
// Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
|
|
|
|
// Copyright (C) 2003 Ben Maurer
|
|
|
|
// Copyright 2011 Xamarin Inc (http://www.xamarin.com).
|
|
|
|
//
|
|
|
|
// Author: Jim Richardson, develop@wtfo-guru.com
|
|
|
|
// Dan Lewis (dihlewis@yahoo.co.uk)
|
|
|
|
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
|
|
|
// Ben Maurer (bmaurer@users.sourceforge.net)
|
|
|
|
// Sebastien Pouliot <sebastien@ximian.com>
|
|
|
|
// Created: Saturday, August 11, 2001
|
|
|
|
//
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
//
|
|
|
|
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
// a copy of this software and associated documentation files (the
|
|
|
|
// "Software"), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
// the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be
|
|
|
|
// included in all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
//
|
|
|
|
|
|
|
|
using System.Globalization;
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
using System.Security;
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Security.Permissions;
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
namespace System.IO {
|
|
|
|
|
|
|
|
[ComVisible (true)]
|
|
|
|
public static class Path {
|
|
|
|
|
|
|
|
[Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
|
|
|
|
public static readonly char[] InvalidPathChars;
|
|
|
|
public static readonly char AltDirectorySeparatorChar;
|
|
|
|
public static readonly char DirectorySeparatorChar;
|
|
|
|
public static readonly char PathSeparator;
|
|
|
|
internal static readonly string DirectorySeparatorStr;
|
|
|
|
public static readonly char VolumeSeparatorChar;
|
|
|
|
|
|
|
|
internal static readonly char[] PathSeparatorChars;
|
|
|
|
private static readonly bool dirEqualsVolume;
|
|
|
|
|
|
|
|
// class methods
|
|
|
|
public static string ChangeExtension (string path, string extension)
|
|
|
|
{
|
|
|
|
if (path == null)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (path.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
int iExt = findExtension (path);
|
|
|
|
|
|
|
|
if (extension == null)
|
|
|
|
return iExt < 0 ? path : path.Substring (0, iExt);
|
|
|
|
else if (extension.Length == 0)
|
|
|
|
return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
|
|
|
|
|
|
|
|
else if (path.Length != 0) {
|
|
|
|
if (extension.Length > 0 && extension [0] != '.')
|
|
|
|
extension = "." + extension;
|
|
|
|
} else
|
|
|
|
extension = String.Empty;
|
|
|
|
|
|
|
|
if (iExt < 0) {
|
|
|
|
return path + extension;
|
|
|
|
} else if (iExt > 0) {
|
|
|
|
string temp = path.Substring (0, iExt);
|
|
|
|
return temp + extension;
|
|
|
|
}
|
|
|
|
|
|
|
|
return extension;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string Combine (string path1, string path2)
|
|
|
|
{
|
|
|
|
if (path1 == null)
|
|
|
|
throw new ArgumentNullException ("path1");
|
|
|
|
|
|
|
|
if (path2 == null)
|
|
|
|
throw new ArgumentNullException ("path2");
|
|
|
|
|
|
|
|
if (path1.Length == 0)
|
|
|
|
return path2;
|
|
|
|
|
|
|
|
if (path2.Length == 0)
|
|
|
|
return path1;
|
|
|
|
|
|
|
|
if (path1.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
if (path2.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
//TODO???: UNC names
|
|
|
|
if (IsPathRooted (path2))
|
|
|
|
return path2;
|
|
|
|
|
|
|
|
char p1end = path1 [path1.Length - 1];
|
|
|
|
if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
|
|
|
|
return path1 + DirectorySeparatorStr + path2;
|
|
|
|
|
|
|
|
return path1 + path2;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// This routine:
|
|
|
|
// * Removes duplicat path separators from a string
|
|
|
|
// * If the string starts with \\, preserves the first two (hostname on Windows)
|
|
|
|
// * Removes the trailing path separator.
|
|
|
|
// * Returns the DirectorySeparatorChar for the single input DirectorySeparatorChar or AltDirectorySeparatorChar
|
|
|
|
//
|
|
|
|
// Unlike CanonicalizePath, this does not do any path resolution
|
|
|
|
// (which GetDirectoryName is not supposed to do).
|
|
|
|
//
|
|
|
|
internal static string CleanPath (string s)
|
|
|
|
{
|
|
|
|
int l = s.Length;
|
|
|
|
int sub = 0;
|
2016-11-10 13:04:39 +00:00
|
|
|
int alt = 0;
|
2014-08-13 10:39:27 +01:00
|
|
|
int start = 0;
|
|
|
|
|
|
|
|
// Host prefix?
|
|
|
|
char s0 = s [0];
|
|
|
|
if (l > 2 && s0 == '\\' && s [1] == '\\'){
|
|
|
|
start = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are only left with root
|
|
|
|
if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
|
|
|
|
return s;
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
for (int i = start; i < l; i++){
|
|
|
|
char c = s [i];
|
|
|
|
|
|
|
|
if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
|
|
|
|
continue;
|
2016-11-10 13:04:39 +00:00
|
|
|
if (DirectorySeparatorChar != AltDirectorySeparatorChar && c == AltDirectorySeparatorChar)
|
|
|
|
alt++;
|
2014-08-13 10:39:27 +01:00
|
|
|
if (i+1 == l)
|
|
|
|
sub++;
|
|
|
|
else {
|
|
|
|
c = s [i + 1];
|
|
|
|
if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
|
|
|
|
sub++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-10 13:04:39 +00:00
|
|
|
if (sub == 0 && alt == 0)
|
2014-08-13 10:39:27 +01:00
|
|
|
return s;
|
|
|
|
|
|
|
|
char [] copy = new char [l-sub];
|
|
|
|
if (start != 0){
|
|
|
|
copy [0] = '\\';
|
|
|
|
copy [1] = '\\';
|
|
|
|
}
|
|
|
|
for (int i = start, j = start; i < l && j < copy.Length; i++){
|
|
|
|
char c = s [i];
|
|
|
|
|
|
|
|
if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
|
|
|
|
copy [j++] = c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For non-trailing cases.
|
|
|
|
if (j+1 != copy.Length){
|
|
|
|
copy [j++] = DirectorySeparatorChar;
|
|
|
|
for (;i < l-1; i++){
|
|
|
|
c = s [i+1];
|
|
|
|
if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new String (copy);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetDirectoryName (string path)
|
|
|
|
{
|
|
|
|
// LAMESPEC: For empty string MS docs say both
|
|
|
|
// return null AND throw exception. Seems .NET throws.
|
|
|
|
if (path == String.Empty)
|
|
|
|
throw new ArgumentException("Invalid path");
|
|
|
|
|
|
|
|
if (path == null || GetPathRoot (path) == path)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (path.Trim ().Length == 0)
|
|
|
|
throw new ArgumentException ("Argument string consists of whitespace characters only.");
|
|
|
|
|
|
|
|
if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
|
|
|
|
throw new ArgumentException ("Path contains invalid characters");
|
|
|
|
|
|
|
|
int nLast = path.LastIndexOfAny (PathSeparatorChars);
|
|
|
|
if (nLast == 0)
|
|
|
|
nLast++;
|
|
|
|
|
|
|
|
if (nLast > 0) {
|
|
|
|
string ret = path.Substring (0, nLast);
|
|
|
|
int l = ret.Length;
|
|
|
|
|
|
|
|
if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
|
|
|
|
return ret + DirectorySeparatorChar;
|
|
|
|
else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
|
|
|
|
return ret + VolumeSeparatorChar;
|
|
|
|
else {
|
|
|
|
//
|
|
|
|
// Important: do not use CanonicalizePath here, use
|
|
|
|
// the custom CleanPath here, as this should not
|
|
|
|
// return absolute paths
|
|
|
|
//
|
|
|
|
return CleanPath (ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return String.Empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetExtension (string path)
|
|
|
|
{
|
|
|
|
if (path == null)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (path.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
int iExt = findExtension (path);
|
|
|
|
|
|
|
|
if (iExt > -1)
|
|
|
|
{
|
|
|
|
if (iExt < path.Length - 1)
|
|
|
|
return path.Substring (iExt);
|
|
|
|
}
|
|
|
|
return string.Empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetFileName (string path)
|
|
|
|
{
|
|
|
|
if (path == null || path.Length == 0)
|
|
|
|
return path;
|
|
|
|
|
|
|
|
if (path.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
int nLast = path.LastIndexOfAny (PathSeparatorChars);
|
|
|
|
if (nLast >= 0)
|
|
|
|
return path.Substring (nLast + 1);
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetFileNameWithoutExtension (string path)
|
|
|
|
{
|
|
|
|
return ChangeExtension (GetFileName (path), null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetFullPath (string path)
|
|
|
|
{
|
|
|
|
string fullpath = InsecureGetFullPath (path);
|
|
|
|
|
|
|
|
SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
|
|
|
|
|
2016-11-10 13:04:39 +00:00
|
|
|
#if !MOBILE
|
2014-08-13 10:39:27 +01:00
|
|
|
if (SecurityManager.SecurityEnabled) {
|
|
|
|
new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return fullpath;
|
|
|
|
}
|
|
|
|
|
2016-08-03 10:59:49 +00:00
|
|
|
internal static String GetFullPathInternal(String path)
|
|
|
|
{
|
|
|
|
return InsecureGetFullPath (path);
|
|
|
|
}
|
|
|
|
|
2016-02-22 11:00:01 -05:00
|
|
|
#if !MOBILE
|
2014-08-13 10:39:27 +01:00
|
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
|
|
|
|
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
|
|
private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
|
|
|
|
|
|
|
|
internal static string GetFullPathName(string path)
|
|
|
|
{
|
|
|
|
const int MAX_PATH = 260;
|
|
|
|
StringBuilder buffer = new StringBuilder(MAX_PATH);
|
|
|
|
IntPtr ptr = IntPtr.Zero;
|
|
|
|
int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
|
|
|
|
if (length == 0)
|
|
|
|
{
|
|
|
|
int error = Marshal.GetLastWin32Error();
|
|
|
|
throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
|
|
|
|
}
|
|
|
|
else if (length > MAX_PATH)
|
|
|
|
{
|
|
|
|
buffer = new StringBuilder(length);
|
|
|
|
GetFullPathName(path, length, buffer, ref ptr);
|
|
|
|
}
|
|
|
|
return buffer.ToString();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static string WindowsDriveAdjustment (string path)
|
|
|
|
{
|
2016-11-10 13:04:39 +00:00
|
|
|
// three special cases to consider when a drive is specified
|
|
|
|
if (path.Length < 2) {
|
|
|
|
if (path.Length == 1 && (path[0] == '\\' || path[0] == '/'))
|
|
|
|
return Path.GetPathRoot(Directory.GetCurrentDirectory());
|
2014-08-13 10:39:27 +01:00
|
|
|
return path;
|
2016-11-10 13:04:39 +00:00
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
if ((path [1] != ':') || !Char.IsLetter (path [0]))
|
|
|
|
return path;
|
|
|
|
|
|
|
|
string current = Directory.InsecureGetCurrentDirectory ();
|
|
|
|
// first, only the drive is specified
|
|
|
|
if (path.Length == 2) {
|
|
|
|
// then if the current directory is on the same drive
|
|
|
|
if (current [0] == path [0])
|
|
|
|
path = current; // we return it
|
|
|
|
else
|
|
|
|
path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
|
|
|
|
} else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
|
|
|
|
// second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
|
|
|
|
// If the current directory is on the specified drive...
|
|
|
|
if (current [0] == path [0]) {
|
|
|
|
// then specified directory is appended to the current drive directory
|
|
|
|
path = Path.Combine (current, path.Substring (2, path.Length - 2));
|
|
|
|
} else {
|
|
|
|
// we have to use the GetFullPathName Windows API
|
|
|
|
path = GetFullPathName(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
}
|
2016-02-22 11:00:01 -05:00
|
|
|
#endif
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
// insecure - do not call directly
|
|
|
|
internal static string InsecureGetFullPath (string path)
|
|
|
|
{
|
|
|
|
if (path == null)
|
|
|
|
throw new ArgumentNullException ("path");
|
|
|
|
|
|
|
|
if (path.Trim ().Length == 0) {
|
|
|
|
string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
|
|
|
|
throw new ArgumentException (msg);
|
|
|
|
}
|
2016-02-22 11:00:01 -05:00
|
|
|
#if !MOBILE
|
2014-08-13 10:39:27 +01:00
|
|
|
// adjust for drives, i.e. a special case for windows
|
|
|
|
if (Environment.IsRunningOnWindows)
|
|
|
|
path = WindowsDriveAdjustment (path);
|
2016-02-22 11:00:01 -05:00
|
|
|
#endif
|
2014-08-13 10:39:27 +01:00
|
|
|
// if the supplied path ends with a separator...
|
|
|
|
char end = path [path.Length - 1];
|
|
|
|
|
|
|
|
var canonicalize = true;
|
|
|
|
if (path.Length >= 2 &&
|
2015-04-07 09:35:12 +01:00
|
|
|
IsDirectorySeparator (path [0]) &&
|
|
|
|
IsDirectorySeparator (path [1])) {
|
2014-08-13 10:39:27 +01:00
|
|
|
if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
|
|
|
|
throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
|
|
|
|
|
|
|
|
if (path [0] != DirectorySeparatorChar)
|
|
|
|
path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if (!IsPathRooted (path)) {
|
|
|
|
|
|
|
|
// avoid calling expensive CanonicalizePath when possible
|
|
|
|
if (!Environment.IsRunningOnWindows) {
|
|
|
|
var start = 0;
|
|
|
|
while ((start = path.IndexOf ('.', start)) != -1) {
|
|
|
|
if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
canonicalize = start > 0;
|
|
|
|
}
|
|
|
|
|
2014-10-04 11:27:48 +01:00
|
|
|
var cwd = Directory.InsecureGetCurrentDirectory();
|
|
|
|
if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
|
|
|
|
path = cwd + path;
|
|
|
|
else
|
|
|
|
path = cwd + DirectorySeparatorChar + path;
|
2014-08-13 10:39:27 +01:00
|
|
|
} else if (DirectorySeparatorChar == '\\' &&
|
|
|
|
path.Length >= 2 &&
|
2015-04-07 09:35:12 +01:00
|
|
|
IsDirectorySeparator (path [0]) &&
|
|
|
|
!IsDirectorySeparator (path [1])) { // like `\abc\def'
|
2014-08-13 10:39:27 +01:00
|
|
|
string current = Directory.InsecureGetCurrentDirectory();
|
|
|
|
if (current [1] == VolumeSeparatorChar)
|
|
|
|
path = current.Substring (0, 2) + path;
|
|
|
|
else
|
2015-08-26 07:17:56 -04:00
|
|
|
path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (canonicalize)
|
|
|
|
path = CanonicalizePath (path);
|
|
|
|
|
|
|
|
// if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
|
2015-04-07 09:35:12 +01:00
|
|
|
if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
|
2014-08-13 10:39:27 +01:00
|
|
|
path += DirectorySeparatorChar;
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2015-04-07 09:35:12 +01:00
|
|
|
internal static bool IsDirectorySeparator (char c) {
|
2014-08-13 10:39:27 +01:00
|
|
|
return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
|
|
|
|
}
|
2015-04-07 09:35:12 +01:00
|
|
|
|
2014-08-13 10:39:27 +01:00
|
|
|
public static string GetPathRoot (string path)
|
|
|
|
{
|
|
|
|
if (path == null)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (path.Trim ().Length == 0)
|
|
|
|
throw new ArgumentException ("The specified path is not of a legal form.");
|
|
|
|
|
|
|
|
if (!IsPathRooted (path))
|
|
|
|
return String.Empty;
|
|
|
|
|
|
|
|
if (DirectorySeparatorChar == '/') {
|
|
|
|
// UNIX
|
2015-04-07 09:35:12 +01:00
|
|
|
return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
|
2014-08-13 10:39:27 +01:00
|
|
|
} else {
|
|
|
|
// Windows
|
|
|
|
int len = 2;
|
|
|
|
|
2015-04-07 09:35:12 +01:00
|
|
|
if (path.Length == 1 && IsDirectorySeparator (path [0]))
|
2014-08-13 10:39:27 +01:00
|
|
|
return DirectorySeparatorStr;
|
|
|
|
else if (path.Length < 2)
|
|
|
|
return String.Empty;
|
|
|
|
|
2015-04-07 09:35:12 +01:00
|
|
|
if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
|
2014-08-13 10:39:27 +01:00
|
|
|
// UNC: \\server or \\server\share
|
|
|
|
// Get server
|
2015-04-07 09:35:12 +01:00
|
|
|
while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
// Get share
|
|
|
|
if (len < path.Length) {
|
|
|
|
len++;
|
2015-04-07 09:35:12 +01:00
|
|
|
while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return DirectorySeparatorStr +
|
|
|
|
DirectorySeparatorStr +
|
|
|
|
path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
|
2015-04-07 09:35:12 +01:00
|
|
|
} else if (IsDirectorySeparator (path [0])) {
|
2014-08-13 10:39:27 +01:00
|
|
|
// path starts with '\' or '/'
|
|
|
|
return DirectorySeparatorStr;
|
|
|
|
} else if (path[1] == VolumeSeparatorChar) {
|
|
|
|
// C:\folder
|
2015-04-07 09:35:12 +01:00
|
|
|
if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
|
2014-08-13 10:39:27 +01:00
|
|
|
} else
|
|
|
|
return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
|
|
|
|
return path.Substring (0, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Further limit the assertion when imperative Assert is implemented
|
|
|
|
[FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
|
|
|
|
public static string GetTempFileName ()
|
|
|
|
{
|
|
|
|
FileStream f = null;
|
|
|
|
string path;
|
|
|
|
Random rnd;
|
|
|
|
int num;
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
|
|
|
|
|
|
|
|
rnd = new Random ();
|
|
|
|
var tmp_path = GetTempPath ();
|
|
|
|
do {
|
|
|
|
num = rnd.Next ();
|
|
|
|
num++;
|
|
|
|
path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
|
|
|
|
|
|
|
|
try {
|
|
|
|
f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
|
|
|
|
8192, false, (FileOptions) 1);
|
|
|
|
} catch (IOException ex){
|
2016-08-03 10:59:49 +00:00
|
|
|
if (ex._HResult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
|
2014-08-13 10:39:27 +01:00
|
|
|
throw;
|
|
|
|
} catch (UnauthorizedAccessException ex) {
|
|
|
|
if (count ++ > 65536)
|
|
|
|
throw new IOException (ex.Message, ex);
|
|
|
|
}
|
|
|
|
} while (f == null);
|
|
|
|
|
|
|
|
f.Close();
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
[EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
|
|
|
|
public static string GetTempPath ()
|
|
|
|
{
|
|
|
|
SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
|
|
|
|
|
|
|
|
string p = get_temp_path ();
|
|
|
|
if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
|
|
|
|
return p + DirectorySeparatorChar;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImplAttribute(MethodImplOptions.InternalCall)]
|
|
|
|
private static extern string get_temp_path ();
|
|
|
|
|
|
|
|
public static bool HasExtension (string path)
|
|
|
|
{
|
|
|
|
if (path == null || path.Trim ().Length == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (path.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
int pos = findExtension (path);
|
|
|
|
return 0 <= pos && pos < path.Length - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static bool IsPathRooted (string path)
|
|
|
|
{
|
|
|
|
if (path == null || path.Length == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (path.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
char c = path [0];
|
|
|
|
return (c == DirectorySeparatorChar ||
|
|
|
|
c == AltDirectorySeparatorChar ||
|
|
|
|
(!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static char[] GetInvalidFileNameChars ()
|
|
|
|
{
|
|
|
|
// return a new array as we do not want anyone to be able to change the values
|
|
|
|
if (Environment.IsRunningOnWindows) {
|
|
|
|
return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
|
|
|
'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
|
|
|
|
'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
|
|
|
|
'\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
|
|
|
|
} else {
|
|
|
|
return new char [2] { '\x00', '/' };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static char[] GetInvalidPathChars ()
|
|
|
|
{
|
|
|
|
// return a new array as we do not want anyone to be able to change the values
|
|
|
|
if (Environment.IsRunningOnWindows) {
|
|
|
|
return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
|
|
|
'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
|
|
|
|
'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
|
|
|
|
'\x1E', '\x1F' };
|
|
|
|
} else {
|
|
|
|
return new char [1] { '\x00' };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetRandomFileName ()
|
|
|
|
{
|
|
|
|
// returns a 8.3 filename (total size 12)
|
|
|
|
StringBuilder sb = new StringBuilder (12);
|
|
|
|
// using strong crypto but without creating the file
|
|
|
|
RandomNumberGenerator rng = RandomNumberGenerator.Create ();
|
|
|
|
byte [] buffer = new byte [11];
|
|
|
|
rng.GetBytes (buffer);
|
|
|
|
|
|
|
|
for (int i = 0; i < buffer.Length; i++) {
|
|
|
|
if (sb.Length == 8)
|
|
|
|
sb.Append ('.');
|
|
|
|
|
|
|
|
// restrict to length of range [a..z0..9]
|
|
|
|
int b = (buffer [i] % 36);
|
|
|
|
char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
|
|
|
|
sb.Append (c);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sb.ToString ();
|
|
|
|
}
|
|
|
|
|
|
|
|
// private class methods
|
|
|
|
|
|
|
|
private static int findExtension (string path)
|
|
|
|
{
|
|
|
|
// method should return the index of the path extension
|
|
|
|
// start or -1 if no valid extension
|
|
|
|
if (path != null){
|
|
|
|
int iLastDot = path.LastIndexOf ('.');
|
|
|
|
int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
|
|
|
|
|
|
|
|
if (iLastDot > iLastSep)
|
|
|
|
return iLastDot;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Path ()
|
|
|
|
{
|
|
|
|
VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
|
|
|
|
DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
|
|
|
|
AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
|
|
|
|
|
|
|
|
PathSeparator = MonoIO.PathSeparator;
|
|
|
|
// this copy will be modifiable ("by design")
|
|
|
|
InvalidPathChars = GetInvalidPathChars ();
|
|
|
|
// internal fields
|
|
|
|
|
|
|
|
DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
|
|
|
|
PathSeparatorChars = new char [] {
|
|
|
|
DirectorySeparatorChar,
|
|
|
|
AltDirectorySeparatorChar,
|
|
|
|
VolumeSeparatorChar
|
|
|
|
};
|
|
|
|
|
|
|
|
dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns the server and share part of a UNC. Assumes "path" is a UNC.
|
|
|
|
static string GetServerAndShare (string path)
|
|
|
|
{
|
|
|
|
int len = 2;
|
2015-04-07 09:35:12 +01:00
|
|
|
while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
if (len < path.Length) {
|
|
|
|
len++;
|
2015-04-07 09:35:12 +01:00
|
|
|
while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
|
|
|
|
}
|
|
|
|
|
|
|
|
// assumes Environment.IsRunningOnWindows == true
|
|
|
|
static bool SameRoot (string root, string path)
|
|
|
|
{
|
|
|
|
// compare root - if enough details are available
|
|
|
|
if ((root.Length < 2) || (path.Length < 2))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// UNC handling
|
2015-04-07 09:35:12 +01:00
|
|
|
if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
|
|
|
|
if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
|
2014-08-13 10:39:27 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
string rootShare = GetServerAndShare (root);
|
|
|
|
string pathShare = GetServerAndShare (path);
|
|
|
|
|
|
|
|
return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// same volume/drive
|
|
|
|
if (!root [0].Equals (path [0]))
|
|
|
|
return false;
|
|
|
|
// presence of the separator
|
|
|
|
if (path[1] != Path.VolumeSeparatorChar)
|
|
|
|
return false;
|
|
|
|
if ((root.Length > 2) && (path.Length > 2)) {
|
|
|
|
// but don't directory compare the directory separator
|
2015-04-07 09:35:12 +01:00
|
|
|
return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static string CanonicalizePath (string path)
|
|
|
|
{
|
|
|
|
// STEP 1: Check for empty string
|
|
|
|
if (path == null)
|
|
|
|
return path;
|
|
|
|
if (Environment.IsRunningOnWindows)
|
|
|
|
path = path.Trim ();
|
|
|
|
|
|
|
|
if (path.Length == 0)
|
|
|
|
return path;
|
|
|
|
|
|
|
|
// STEP 2: Check to see if this is only a root
|
|
|
|
string root = Path.GetPathRoot (path);
|
|
|
|
// it will return '\' for path '\', while it should return 'c:\' or so.
|
|
|
|
// Note: commenting this out makes the need for the (target == 1...) check in step 5
|
|
|
|
//if (root == path) return path;
|
|
|
|
|
|
|
|
// STEP 3: split the directories, this gets rid of consecutative "/"'s
|
|
|
|
string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
|
|
// STEP 4: Get rid of directories containing . and ..
|
|
|
|
int target = 0;
|
|
|
|
|
|
|
|
bool isUnc = Environment.IsRunningOnWindows &&
|
2015-04-07 09:35:12 +01:00
|
|
|
root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
// Set an overwrite limit for UNC paths since '\' + server + share
|
|
|
|
// must not be eliminated by the '..' elimination algorithm.
|
|
|
|
int limit = isUnc ? 3 : 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < dirs.Length; i++) {
|
|
|
|
// WIN32 path components must be trimmed
|
|
|
|
if (Environment.IsRunningOnWindows)
|
|
|
|
dirs[i] = dirs[i].TrimEnd ();
|
|
|
|
|
|
|
|
if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
|
|
|
|
continue;
|
|
|
|
else if (dirs[i] == "..") {
|
|
|
|
// don't overwrite path segments below the limit
|
|
|
|
if (target > limit)
|
|
|
|
target--;
|
|
|
|
} else
|
|
|
|
dirs[target++] = dirs[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// STEP 5: Combine everything.
|
|
|
|
if (target == 0 || (target == 1 && dirs[0] == ""))
|
|
|
|
return root;
|
|
|
|
else {
|
|
|
|
string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
|
|
|
|
if (Environment.IsRunningOnWindows) {
|
|
|
|
// append leading '\' of the UNC path that was lost in STEP 3.
|
|
|
|
if (isUnc)
|
|
|
|
ret = Path.DirectorySeparatorStr + ret;
|
|
|
|
|
|
|
|
if (!SameRoot (root, ret))
|
|
|
|
ret = root + ret;
|
|
|
|
|
|
|
|
if (isUnc) {
|
|
|
|
return ret;
|
2015-04-07 09:35:12 +01:00
|
|
|
} else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
|
2014-08-13 10:39:27 +01:00
|
|
|
if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
|
|
|
|
ret += Path.DirectorySeparatorChar;
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
string current = Directory.GetCurrentDirectory ();
|
|
|
|
if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
|
|
|
|
// DOS local file path
|
2015-04-07 09:35:12 +01:00
|
|
|
if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
|
2014-08-13 10:39:27 +01:00
|
|
|
ret += '\\';
|
|
|
|
return current.Substring (0, 2) + ret;
|
2015-04-07 09:35:12 +01:00
|
|
|
} else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
|
2014-08-13 10:39:27 +01:00
|
|
|
return current + ret.Substring (1);
|
|
|
|
else
|
|
|
|
return current + ret;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (root != "" && ret.Length > 0 && ret [0] != '/')
|
|
|
|
ret = root + ret;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// required for FileIOPermission (and most proibably reusable elsewhere too)
|
|
|
|
// both path MUST be "full paths"
|
|
|
|
static internal bool IsPathSubsetOf (string subset, string path)
|
|
|
|
{
|
|
|
|
if (subset.Length > path.Length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// check that everything up to the last separator match
|
|
|
|
int slast = subset.LastIndexOfAny (PathSeparatorChars);
|
|
|
|
if (String.Compare (subset, 0, path, 0, slast) != 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
slast++;
|
|
|
|
// then check if the last segment is identical
|
|
|
|
int plast = path.IndexOfAny (PathSeparatorChars, slast);
|
|
|
|
if (plast >= slast) {
|
|
|
|
return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
|
|
|
|
}
|
|
|
|
if (subset.Length != path.Length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public
|
|
|
|
static string Combine (params string [] paths)
|
|
|
|
{
|
|
|
|
if (paths == null)
|
|
|
|
throw new ArgumentNullException ("paths");
|
|
|
|
|
|
|
|
bool need_sep;
|
|
|
|
var ret = new StringBuilder ();
|
|
|
|
int pathsLen = paths.Length;
|
|
|
|
int slen;
|
|
|
|
need_sep = false;
|
|
|
|
|
|
|
|
foreach (var s in paths) {
|
|
|
|
if (s == null)
|
|
|
|
throw new ArgumentNullException ("One of the paths contains a null value", "paths");
|
|
|
|
if (s.Length == 0)
|
|
|
|
continue;
|
|
|
|
if (s.IndexOfAny (InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException ("Illegal characters in path.");
|
|
|
|
|
|
|
|
if (need_sep) {
|
|
|
|
need_sep = false;
|
|
|
|
ret.Append (DirectorySeparatorStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
pathsLen--;
|
|
|
|
if (IsPathRooted (s))
|
|
|
|
ret.Length = 0;
|
|
|
|
|
|
|
|
ret.Append (s);
|
|
|
|
slen = s.Length;
|
|
|
|
if (slen > 0 && pathsLen > 0) {
|
|
|
|
char p1end = s [slen - 1];
|
|
|
|
if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
|
|
|
|
need_sep = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret.ToString ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public
|
|
|
|
static string Combine (string path1, string path2, string path3)
|
|
|
|
{
|
|
|
|
if (path1 == null)
|
|
|
|
throw new ArgumentNullException ("path1");
|
|
|
|
|
|
|
|
if (path2 == null)
|
|
|
|
throw new ArgumentNullException ("path2");
|
|
|
|
|
|
|
|
if (path3 == null)
|
|
|
|
throw new ArgumentNullException ("path3");
|
|
|
|
|
|
|
|
return Combine (new string [] { path1, path2, path3 });
|
|
|
|
}
|
|
|
|
|
|
|
|
public
|
|
|
|
static string Combine (string path1, string path2, string path3, string path4)
|
|
|
|
{
|
|
|
|
if (path1 == null)
|
|
|
|
throw new ArgumentNullException ("path1");
|
|
|
|
|
|
|
|
if (path2 == null)
|
|
|
|
throw new ArgumentNullException ("path2");
|
|
|
|
|
|
|
|
if (path3 == null)
|
|
|
|
throw new ArgumentNullException ("path3");
|
|
|
|
|
|
|
|
if (path4 == null)
|
|
|
|
throw new ArgumentNullException ("path4");
|
|
|
|
|
|
|
|
return Combine (new string [] { path1, path2, path3, path4 });
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static void Validate (string path)
|
|
|
|
{
|
|
|
|
Validate (path, "path");
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static void Validate (string path, string parameterName)
|
|
|
|
{
|
|
|
|
if (path == null)
|
|
|
|
throw new ArgumentNullException (parameterName);
|
|
|
|
if (String.IsNullOrWhiteSpace (path))
|
|
|
|
throw new ArgumentException (Locale.GetText ("Path is empty"));
|
|
|
|
if (path.IndexOfAny (Path.InvalidPathChars) != -1)
|
|
|
|
throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
|
|
|
|
if (Environment.IsRunningOnWindows) {
|
|
|
|
int idx = path.IndexOf (':');
|
|
|
|
if (idx >= 0 && idx != 1)
|
|
|
|
throw new ArgumentException (parameterName);
|
|
|
|
}
|
|
|
|
}
|
2016-08-03 10:59:49 +00:00
|
|
|
|
|
|
|
internal static string DirectorySeparatorCharAsString {
|
|
|
|
get {
|
|
|
|
return DirectorySeparatorStr;
|
|
|
|
}
|
|
|
|
}
|
2016-11-10 13:04:39 +00:00
|
|
|
|
|
|
|
internal const int MAX_PATH = 260; // From WinDef.h
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
}
|