#region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Security.AccessControl; using System.Security.Principal; using System.Text; using System.IO.Compression; namespace CSharpTest.Net.Utils { /// /// Provides utilities related to files and file paths /// public static class FileUtils { private static readonly string FileNotFoundMessage; private static readonly char[] IllegalFileNameChars; static FileUtils() { FileNotFoundMessage = new FileNotFoundException().Message; Dictionary bad = new Dictionary(); foreach (char ch in new char[] { '/', '\\', ':', '*', '?', '"', '<', '>', '|' }) bad[ch] = ch; foreach (char ch in Path.GetInvalidFileNameChars()) bad[ch] = ch; List badChars = new List(bad.Keys); badChars.Sort(); IllegalFileNameChars = badChars.ToArray(); } /// /// Returns true if the extension provided contains only one '.' at the beginning /// of the string and does not contain any path or invalid filename characters. /// public static bool IsValidExtension(string fileExt) { if (String.IsNullOrEmpty(fileExt) || fileExt.Trim() != fileExt || fileExt.LastIndexOf('.') != 0 || fileExt.IndexOfAny(IllegalFileNameChars) >= 0) return false; return true; } /// /// Returns true if the name provided contains only valid filename characters /// public static bool IsValidFileName(string filename) { if(String.IsNullOrEmpty(filename) || filename.Trim() != filename || filename.Trim('.', ' ', '\t').Length == 0 || filename.IndexOfAny(IllegalFileNameChars) >= 0) return false; return true; } /// /// Creates a valid filename by removing all invalid characters. /// public static string MakeValidFileName(string filename) { return MakeValidFileName(filename, String.Empty); } /// /// Creates a valid filename by replacing all invalid characters with the string provided. /// public static string MakeValidFileName(string filename, string replaceWithChars) { if (Check.NotNull(replaceWithChars).Length > 0 && !IsValidFileName(replaceWithChars)) throw new ArgumentException(); if (IsValidFileName(filename)) return filename; StringBuilder sbpath = new StringBuilder(); bool replaced = false; foreach (Char ch in filename) { bool invalid = Array.BinarySearch(IllegalFileNameChars, ch) >= 0; if (!replaced && invalid) sbpath.Append(replaceWithChars); else if (!invalid) sbpath.Append(ch); replaced = invalid; } filename = sbpath.ToString().Trim(); if (!IsValidFileName(filename)) throw new ArgumentException(); return filename; } /// /// Returns the fully qualified path to the file if it is fully-qualified, exists in the current directory, or /// in the environment path, otherwise generates a FileNotFoundException exception. /// [System.Diagnostics.DebuggerNonUserCode] public static string FindFullPath(string location) { string result; if (TrySearchPath(location, out result)) return result; throw new FileNotFoundException(FileNotFoundMessage, location); } /// /// Expands environment variables into text, i.e. %SystemRoot%, or %ProgramFiles% /// public static String ExpandEnvironment(string input) { return Environment.ExpandEnvironmentVariables(input); } /// /// Returns true if the file is fully-qualified, exists in the current directory, or in the environment path, /// otherwise generates a FileNotFoundException exception. Will not propagate errors. /// public static bool TrySearchPath(string location, out string fullPath) { fullPath = null; try { if (File.Exists(location)) { fullPath = Path.GetFullPath(location); return true; } if (!IsValidFileName(location)) return false; foreach (string pathentry in Environment.GetEnvironmentVariable("PATH").Split(';')) { string testPath = pathentry.Trim(); if (testPath.Length > 0 && Directory.Exists(testPath) && File.Exists(Path.Combine(testPath, location))) { fullPath = Path.GetFullPath(Path.Combine(testPath, location)); return true; } } } catch (Exception error) { Trace.TraceError("{0}", error); } return false; } /// /// For this to work for a directory the argument should end with a '\' character /// public static string MakeRelativePath(string startFile, string targetFile) { StringBuilder newpath = new StringBuilder(); if (startFile == null || targetFile == null) return null; if (startFile == targetFile) return Path.GetFileName(targetFile); List sfpath = new List(startFile.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); List tfpath = new List(targetFile.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); for (int i = sfpath.Count - 1; i >= 0; i--) if (sfpath[i] == ".") sfpath.RemoveAt(i); for (int i = tfpath.Count - 1; i >= 0; i--) if (tfpath[i] == ".") tfpath.RemoveAt(i); int cmpdepth = Math.Min(sfpath.Count - 1, tfpath.Count - 1); int ixdiff = 0; for (; ixdiff < cmpdepth; ixdiff++) if (false == StringComparer.OrdinalIgnoreCase.Equals(sfpath[ixdiff], tfpath[ixdiff])) break; if (ixdiff == 0 && Path.IsPathRooted(targetFile)) return targetFile;//new volumes can't be relative for (int i = ixdiff; i < (sfpath.Count - 1); i++) newpath.AppendFormat("..{0}", Path.DirectorySeparatorChar); for (int i = ixdiff; i < tfpath.Count; i++) { newpath.Append(tfpath[i]); if ((i + 1) < tfpath.Count) newpath.Append(Path.DirectorySeparatorChar); } return newpath.ToString(); } /// Grants the user FullControl for the file, returns true if modified, false if already present public static bool GrantFullControlForFile(string filepath, WellKnownSidType sidType) { return GrantFullControlForFile(filepath, sidType, null); } /// Grants the user FullControl for the file, returns true if modified, false if already present public static bool GrantFullControlForFile(string filepath, WellKnownSidType sidType, SecurityIdentifier domain) { FileSecurity sec = File.GetAccessControl(filepath); SecurityIdentifier sid = new SecurityIdentifier(sidType, domain); bool found = false; List toremove = new List(); foreach (FileSystemAccessRule rule in sec.GetAccessRules(true, false, typeof(SecurityIdentifier))) { if (sid.Value == rule.IdentityReference.Value) { if (rule.AccessControlType != AccessControlType.Allow || rule.FileSystemRights != FileSystemRights.FullControl) toremove.Add(rule); else found = true; } } if (!found || toremove.Count > 0) { foreach (FileSystemAccessRule bad in toremove) sec.RemoveAccessRule(bad); sec.AddAccessRule(new FileSystemAccessRule(sid, FileSystemRights.FullControl, AccessControlType.Allow)); File.SetAccessControl(filepath, sec); return true; } return false; } /// Returns the rights assigned to the given SID for this file's ACL public static FileSystemRights GetPermissions(string filepath, WellKnownSidType sidType) { SecurityIdentifier domain = null; FileSecurity sec = File.GetAccessControl(filepath); SecurityIdentifier sid = new SecurityIdentifier(sidType, domain); FileSystemRights rights = 0; foreach (FileSystemAccessRule rule in sec.GetAccessRules(true, false, typeof(SecurityIdentifier))) { if (sid.Value == rule.IdentityReference.Value) { if (rule.AccessControlType == AccessControlType.Allow) rights |= rule.FileSystemRights; else rights &= ~rule.FileSystemRights; } } return rights; } /// Removes any existing access for the user SID supplied and adds the specified rights public static void ReplacePermissions(string filepath, WellKnownSidType sidType, FileSystemRights allow) { FileSecurity sec = File.GetAccessControl(filepath); SecurityIdentifier sid = new SecurityIdentifier(sidType, null); sec.PurgeAccessRules(sid); //remove existing if(allow != default(FileSystemRights)) sec.AddAccessRule(new FileSystemAccessRule(sid, allow, AccessControlType.Allow)); File.SetAccessControl(filepath, sec); } } }