From be455e0970743c4c7085fcdd7ef83412cfdb0165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Kr=C3=BCger?= Date: Fri, 9 Aug 2019 11:18:20 +0200 Subject: [VersionControl] Directory removal is now checked. --- .../GitRepository.cs | 19 ++++-- .../SubversionRepository.cs | 79 +++++++++++----------- .../MonoDevelop.VersionControl/Repository.cs | 4 +- .../MonoDevelop.Core/FileService.cs | 43 ++++++++++++ 4 files changed, 100 insertions(+), 45 deletions(-) (limited to 'main') diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs index 5530af3948..cf2b6d1a44 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs @@ -1594,11 +1594,16 @@ namespace MonoDevelop.VersionControl.Git Revert (path, true, monitor); } } - } else { - // Untracked files are not deleted by the rm command, so delete them now - foreach (var f in localPaths) - if (Directory.Exists (f)) - Directory.Delete (f, true); + } + } + + if (!keepLocal) { + // Untracked files are not deleted by the rm command, so delete them now + foreach (var f in localPaths) { + if (Directory.Exists (f)) { + FileService.AssertCanDeleteDirectory (f, this.RootPath); + Directory.Delete (f, true); + } } } } @@ -1610,8 +1615,10 @@ namespace MonoDevelop.VersionControl.Git foreach (var f in localPaths) { if (File.Exists (f)) File.Delete (f); - else if (Directory.Exists (f)) + else if (Directory.Exists (f)) { + FileService.AssertCanDeleteDirectory (f, RootPath); Directory.Delete (f, true); + } } RunBlockingOperation (() => { diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion/MonoDevelop.VersionControl.Subversion/SubversionRepository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion/MonoDevelop.VersionControl.Subversion/SubversionRepository.cs index 23b8a6ba74..a4072e57e3 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion/MonoDevelop.VersionControl.Subversion/SubversionRepository.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion/MonoDevelop.VersionControl.Subversion/SubversionRepository.cs @@ -17,7 +17,7 @@ namespace MonoDevelop.VersionControl.Subversion { Url = "svn://"; } - + public SubversionRepository (SubversionVersionControl vcs, string url, FilePath rootPath): base (vcs) { Url = url; @@ -35,11 +35,11 @@ namespace MonoDevelop.VersionControl.Subversion public override bool HasChildRepositories { get { return false; } } - + /*public override IEnumerable ChildRepositories { get { List list = new List (); - + foreach (DirectoryEntry ent in Svn.ListUrl (Url, false)) { if (ent.IsDirectory) { SubversionRepository rep = new SubversionRepository (VersionControlSystem, Url + "/" + ent.Name, null); @@ -80,7 +80,7 @@ namespace MonoDevelop.VersionControl.Subversion { return GetVersionInfo (sourcefile, VersionInfoQueryFlags.IgnoreCache).IsVersioned; } - + public override string GetBaseText (FilePath localFile) { return Svn.GetTextBase (localFile); @@ -95,7 +95,7 @@ namespace MonoDevelop.VersionControl.Subversion { return Svn.GetHistory (this, localFile, since); } - + protected override RevisionPath[] OnGetRevisionChanges (Revision revision) { SvnRevision rev = (SvnRevision) revision; @@ -113,7 +113,7 @@ namespace MonoDevelop.VersionControl.Subversion { return Svn.GetDirectoryVersionInfo (this, localDirectory, getRemoteStatus, recursive); } - + protected override VersionControlOperation GetSupportedOperations (VersionInfo vinfo) { return Svn.GetSupportedOperations (this, vinfo, base.GetSupportedOperations (vinfo)); @@ -164,7 +164,7 @@ namespace MonoDevelop.VersionControl.Subversion if (!serverPath.StartsWith ("/", StringComparison.Ordinal) && !url.EndsWith ("/", StringComparison.Ordinal)) url += "/"; url += serverPath; - + string[] paths = new string[] {url}; CreateDirectory (paths, message, monitor); @@ -180,7 +180,7 @@ namespace MonoDevelop.VersionControl.Subversion } Svn.Commit (new FilePath[] { localPath }, message, monitor); - + return new SubversionRepository (VersionControlSystem, paths[0], localPath); } @@ -199,7 +199,7 @@ namespace MonoDevelop.VersionControl.Subversion foreach (string path in localPaths) Svn.Update (path, recurse, monitor); } - + protected override void OnCommit (ChangeSet changeSet, ProgressMonitor monitor) { Svn.Commit (changeSet.Items.Select (it => it.LocalPath).ToArray (), changeSet.GlobalComment, monitor); @@ -251,10 +251,10 @@ namespace MonoDevelop.VersionControl.Subversion // First of all, make a copy of the file string tmp = Path.GetTempFileName (); File.Copy (path, tmp, true); - + // Now revert the status of the file Revert (path, false, monitor); - + // Copy the file over the old one and clean up File.Copy (tmp, path, true); File.Delete (tmp); @@ -265,7 +265,7 @@ namespace MonoDevelop.VersionControl.Subversion if (!IsVersioned (path.ParentDirectory)) { // The file/folder belongs to an unversioned folder. We can add it by versioning the parent // folders up to the root of the repository - + if (!path.IsChildPathOf (RootPath)) throw new InvalidOperationException ("File outside the repository directory"); @@ -292,7 +292,7 @@ namespace MonoDevelop.VersionControl.Subversion Svn.Add (path, recurse, monitor); } } - + public string Root { get { try { @@ -304,7 +304,7 @@ namespace MonoDevelop.VersionControl.Subversion return string.Empty; } } - } + } public override bool CanMoveFilesFrom (Repository srcRepository, FilePath localSrcPath, FilePath localDestPath) { @@ -330,7 +330,7 @@ namespace MonoDevelop.VersionControl.Subversion File.Delete (localDestPath); destIsVersioned = true; } - + VersionInfo srcInfo = GetVersionInfo (localSrcPath, VersionInfoQueryFlags.IgnoreCache); if (srcInfo != null && srcInfo.HasLocalChange (VersionStatus.ScheduledAdd)) { // Subversion automatically detects the rename and moves the new file accordingly. @@ -359,25 +359,25 @@ namespace MonoDevelop.VersionControl.Subversion VersionInfo vinfo = GetVersionInfo (localDestPath, VersionInfoQueryFlags.IgnoreCache); if (!vinfo.HasLocalChange (VersionStatus.ScheduledDelete) && Directory.Exists (localDestPath)) throw new InvalidOperationException ("Cannot move directory. Destination directory already exist."); - + localSrcPath = localSrcPath.FullPath; - + // The target directory does not exist, but it is versioned. It may be because // it is scheduled to delete, or maybe it has been physicaly deleted. In any // case we are going to replace the old directory by the new directory. - + // Revert the old directory, so we can see which files were there so // we can delete or replace them Revert (localDestPath, true, monitor); - + // Get the list of files in the directory to be replaced ArrayList oldFiles = new ArrayList (); GetDirectoryFiles (localDestPath, oldFiles); - + // Get the list of files to move ArrayList newFiles = new ArrayList (); GetDirectoryFiles (localSrcPath, newFiles); - + // Move all new files to the new destination Hashtable copiedFiles = new Hashtable (); Hashtable copiedFolders = new Hashtable (); @@ -386,12 +386,12 @@ namespace MonoDevelop.VersionControl.Subversion string dst = Path.Combine (localDestPath, src.Substring (((string)localSrcPath).Length + 1)); if (File.Exists (dst)) File.Delete (dst); - + // Make sure the target directory exists string destDir = Path.GetDirectoryName (dst); if (!Directory.Exists (destDir)) Directory.CreateDirectory (destDir); - + // If the source file is versioned, make sure the target directory // is also versioned. if (IsVersioned (src)) @@ -413,19 +413,19 @@ namespace MonoDevelop.VersionControl.Subversion foldersToDelete.Add (fd); } } - + // Delete old folders foreach (string folder in foldersToDelete) { Svn.Delete (folder, true, monitor); } - + // Delete the source directory DeleteDirectory (localSrcPath, true, monitor, false); } else { if (Directory.Exists (localDestPath)) throw new InvalidOperationException ("Cannot move directory. Destination directory already exist."); - + VersionInfo srcInfo = GetVersionInfo (localSrcPath, VersionInfoQueryFlags.IgnoreCache); if (srcInfo != null && srcInfo.HasLocalChange (VersionStatus.ScheduledAdd)) { // If the directory is scheduled to add, cancel it, move the directory, and schedule to add it again @@ -442,18 +442,18 @@ namespace MonoDevelop.VersionControl.Subversion } } } - + void MakeDirVersioned (string dir, ProgressMonitor monitor) { if (Directory.Exists (SubversionBackend.GetDirectoryDotSvn (VersionControlSystem, dir))) return; - + // Make the parent versioned string parentDir = Path.GetDirectoryName (dir); if (parentDir == dir || parentDir == "") throw new InvalidOperationException ("Could not create versioned directory."); MakeDirVersioned (parentDir, monitor); - + Add (dir, false, monitor); } @@ -501,6 +501,8 @@ namespace MonoDevelop.VersionControl.Subversion protected override void OnDeleteDirectories (FilePath[] localPaths, bool force, ProgressMonitor monitor, bool keepLocal) { foreach (string path in localPaths) { + if (!keepLocal) + FileService.AssertCanDeleteDirectory (path, RootPath); if (IsVersioned (path)) { string newPath = String.Empty; if (keepLocal) { @@ -521,12 +523,13 @@ namespace MonoDevelop.VersionControl.Subversion Revert (path, false, monitor); } } - } else - Directory.Delete (path, true); + } else { + Directory.Delete (path, true); + } } } } - + public override DiffInfo GenerateDiff (FilePath baseLocalPath, VersionInfo versionInfo) { string diff = Svn.GetUnifiedDiff (versionInfo.LocalPath, false, false); @@ -534,7 +537,7 @@ namespace MonoDevelop.VersionControl.Subversion return GenerateUnifiedDiffInfo (diff, baseLocalPath, new FilePath[] { versionInfo.LocalPath }).FirstOrDefault (); return null; } - + public override DiffInfo[] PathDiff (FilePath localPath, Revision fromRevision, Revision toRevision) { string diff = Svn.GetUnifiedDiff (localPath, (SvnRevision)fromRevision, localPath, (SvnRevision)toRevision, true); @@ -557,7 +560,7 @@ namespace MonoDevelop.VersionControl.Subversion return GenerateUnifiedDiffInfo (diff, baseLocalPath, null); } } - + public override Annotation[] GetAnnotations (FilePath repositoryPath, Revision since) { SvnRevision sinceRev = since != null ? (SvnRevision)since : null; @@ -578,14 +581,14 @@ namespace MonoDevelop.VersionControl.Subversion } } } - + return annotations.ToArray (); } - + public override string CreatePatch (IEnumerable diffs) { StringBuilder patch = new StringBuilder (); - + if (null != diffs) { foreach (DiffInfo diff in diffs) { string relpath; @@ -601,7 +604,7 @@ namespace MonoDevelop.VersionControl.Subversion patch.AppendLine (diff.Content); } } - + return patch.ToString (); } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs index 7ec8059025..e6576ae7e5 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs @@ -845,8 +845,10 @@ namespace MonoDevelop.VersionControl LoggingService.LogError ("Failed to delete directory", e); metadata.SetFailure (); if (!keepLocal) - foreach (var path in localPaths) + foreach (var path in localPaths) { + FileService.AssertCanDeleteDirectory (path, RootPath); Directory.Delete (path, true); + } } } ClearCachedVersionInfo (localPaths); diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs index 25c9ba850e..2de9baa50e 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs @@ -43,6 +43,8 @@ using System.Threading.Tasks; using System.Net; using System.Net.Http; using Microsoft.Extensions.ObjectPool; +using System.Runtime.CompilerServices; +using System.Collections; namespace MonoDevelop.Core { @@ -70,6 +72,8 @@ namespace MonoDevelop.Core static readonly EventQueue eventQueue = new FileServiceEventQueue (); + static HashSet lockedDirectories; + static readonly string applicationRootPath = Path.Combine (PropertyService.EntryAssemblyPath, ".."); public static string ApplicationRootPath { get { @@ -114,6 +118,19 @@ namespace MonoDevelop.Core } } + + static FileService () + { + lockedDirectories = new HashSet (); + lockedDirectories.Add ("/"); + foreach (var value in Enum.GetValues (typeof (Environment.SpecialFolder))) { + var path = Environment.GetFolderPath ((Environment.SpecialFolder)value); + if (string.IsNullOrEmpty (path)) + continue; + lockedDirectories.Add (path); + } + } + /// /// Returns true if the folder is in a case sensitive file system /// @@ -160,6 +177,7 @@ namespace MonoDevelop.Core { Debug.Assert (!String.IsNullOrEmpty (path)); try { + AssertCanDeleteDirectory (path); GetFileSystemForPath (path, true).DeleteDirectory (path); } catch (Exception e) { if (!HandleError (GettextCatalog.GetString ("Can't remove directory {0}", path), e)) @@ -169,6 +187,31 @@ namespace MonoDevelop.Core OnFileRemoved (new FileEventArgs (path, true)); } + /// + /// Checks if a directory can be safely deleted. It checks if the directory is not a system relevant directory. + /// + /// The path to be checked. + /// optional parameter that specifies a required parent of the path. + /// Is thrown when the directory can't be safely deleted. + public static void AssertCanDeleteDirectory (FilePath path, string requiredParentDirectory = null) + { + if (lockedDirectories.Contains (path.FullPath)) { + throw new InvalidOperationException ("Can't delete directory " + path + "."); + } + if (requiredParentDirectory != null) { + var cur = path; + FilePath parent = requiredParentDirectory; + while (true) { + if (cur.IsEmpty) { + throw new InvalidOperationException (path + " needs to be child of " + requiredParentDirectory); + } + if (cur == parent) + return; + cur = cur.ParentDirectory; + } + } + } + public static void RenameFile (string oldName, string newName) { Debug.Assert (!String.IsNullOrEmpty (oldName)); -- cgit v1.2.3