diff options
Diffstat (limited to 'main')
9 files changed, 149 insertions, 53 deletions
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 ad31ccf21e..f2e4f1f90c 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.Unix/MonoDevelop.VersionControl.Subversion.Unix/SvnClient.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion.Unix/MonoDevelop.VersionControl.Subversion.Unix/SvnClient.cs index e636edb686..3d2eb4b297 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion.Unix/MonoDevelop.VersionControl.Subversion.Unix/SvnClient.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Subversion.Unix/MonoDevelop.VersionControl.Subversion.Unix/SvnClient.cs @@ -804,18 +804,19 @@ namespace MonoDevelop.VersionControl.Subversion.Unix nb = new notify_baton (); IntPtr localpool = IntPtr.Zero; + string npath = null; try { localpool = TryStartOperation (monitor); // Using Uri here because the normalization method doesn't remove the redundant port number when using https url = NormalizePath (new Uri(url).ToString(), localpool); - string npath = NormalizePath (path, localpool); + npath = NormalizePath (path, localpool); CheckError (svn.client_checkout (IntPtr.Zero, url, npath, ref rev, recurse, ctx, localpool)); } catch (SubversionException e) { if (e.ErrorCode != 200015) throw; - if (Directory.Exists (path.ParentDirectory)) - FileService.DeleteDirectory (path.ParentDirectory); + if (npath != null && Directory.Exists (npath)) + FileService.DeleteDirectory (npath); } finally { TryEndOperation (localpool); } 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<Repository> ChildRepositories {
get {
List<Repository> list = new List<Repository> ();
-
+
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<DiffInfo> 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..5bab230158 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,14 @@ namespace MonoDevelop.VersionControl LoggingService.LogError ("Failed to delete directory", e); metadata.SetFailure (); if (!keepLocal) - foreach (var path in localPaths) - Directory.Delete (path, true); + try { + foreach (var path in localPaths) { + FileService.AssertCanDeleteDirectory (path, RootPath); + Directory.Delete (path, true); + } + } catch (Exception e2) { + LoggingService.LogInternalError (e2); + } } } ClearCachedVersionInfo (localPaths); diff --git a/main/src/addins/VersionControl/Subversion.Win32/SvnSharpClient.cs b/main/src/addins/VersionControl/Subversion.Win32/SvnSharpClient.cs index 60b0675b39..57170d6a33 100644 --- a/main/src/addins/VersionControl/Subversion.Win32/SvnSharpClient.cs +++ b/main/src/addins/VersionControl/Subversion.Win32/SvnSharpClient.cs @@ -251,8 +251,8 @@ namespace SubversionAddinWindows try {
client.CheckOut (new SvnUriTarget (url, GetRevision (rev)), path, args);
} catch (SvnOperationCanceledException) {
- if (Directory.Exists (path.ParentDirectory))
- FileService.DeleteDirectory (path.ParentDirectory);
+ if (Directory.Exists (path))
+ FileService.DeleteDirectory (path);
}
}
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FilePath.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FilePath.cs index d4cc903875..b77c9b23e3 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FilePath.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FilePath.cs @@ -177,12 +177,14 @@ namespace MonoDevelop.Core public bool IsChildPathOf (FilePath basePath)
{ bool startsWith = fileName.StartsWith (basePath.fileName, PathComparison); - if (startsWith && basePath.fileName [basePath.fileName.Length - 1] != Path.DirectorySeparatorChar) { + + if (startsWith && (basePath.fileName [basePath.fileName.Length - 1] != Path.DirectorySeparatorChar && + basePath.fileName [basePath.fileName.Length - 1] != Path.AltDirectorySeparatorChar)) { // If the last character isn't a path separator character, check whether the string we're searching in
// has more characters than the string we're looking for then check the character. // Otherwise, if the path lengths are equal, we return false. if (fileName.Length > basePath.fileName.Length) - startsWith &= fileName [basePath.fileName.Length] == Path.DirectorySeparatorChar; + startsWith &= fileName [basePath.fileName.Length] == Path.DirectorySeparatorChar || fileName [basePath.fileName.Length] == Path.AltDirectorySeparatorChar; else startsWith = false; } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs index 25c9ba850e..d2d1dee90b 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<FilePath> lockedDirectories; + static readonly string applicationRootPath = Path.Combine (PropertyService.EntryAssemblyPath, ".."); public static string ApplicationRootPath { get { @@ -114,6 +118,18 @@ namespace MonoDevelop.Core } } + + static FileService () + { + lockedDirectories = new HashSet<FilePath> (); + foreach (var value in Enum.GetValues (typeof (Environment.SpecialFolder))) { + var path = (FilePath)Environment.GetFolderPath ((Environment.SpecialFolder)value); + if (string.IsNullOrEmpty (path)) + continue; + lockedDirectories.Add (path.CanonicalPath); + } + } + /// <summary> /// Returns true if the folder is in a case sensitive file system /// </summary> @@ -160,6 +176,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 +186,34 @@ namespace MonoDevelop.Core OnFileRemoved (new FileEventArgs (path, true)); } + /// <summary> + /// Checks if a directory can be safely deleted. It checks if the directory is not a system relevant directory. + /// </summary> + /// <param name="path">The path to be checked.</param> + /// <param name="requiredParentDirectory">optional parameter that specifies a required parent of the path.</param> + /// <param name="includingParent">if true, requiredParentDirectory can be deleted.</param> + /// <exception cref="InvalidOperationException">Is thrown when the directory can't be safely deleted.</exception> + public static void AssertCanDeleteDirectory (FilePath path, string requiredParentDirectory = null, bool includingParent = true) + { + path = path.FullPath.CanonicalPath; + if (lockedDirectories.Contains (path)) { + throw new InvalidOperationException ("Can't delete directory " + path + "."); + } + + foreach (var drive in Directory.GetLogicalDrives ()) { + if (path.Equals (((FilePath)drive).FullPath.CanonicalPath)) + throw new InvalidOperationException ("Can't delete logical drive " + path + "."); + } + + if (requiredParentDirectory != null) { + var parent = ((FilePath)requiredParentDirectory).FullPath.CanonicalPath; + if (!includingParent && parent == path) + throw new InvalidOperationException ("Can't delete parent path" + path); + if (!path.IsChildPathOf (parent) && parent != path) + throw new InvalidOperationException (path + " needs to be child of " + requiredParentDirectory); + } + } + public static void RenameFile (string oldName, string newName) { Debug.Assert (!String.IsNullOrEmpty (oldName)); diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core/FilePathTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core/FilePathTests.cs index 054ce5a4a8..3ce3770320 100644 --- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core/FilePathTests.cs +++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core/FilePathTests.cs @@ -107,6 +107,10 @@ namespace MonoDevelop.Core child = parent.Combine ("child"); Assert.IsTrue (child.IsChildPathOf (parent)); + // Alternative trailing directory char. + parent = FilePath.Build ("base" + Path.AltDirectorySeparatorChar); + Assert.IsTrue (child.IsChildPathOf (parent)); + // https://bugzilla.xamarin.com/show_bug.cgi?id=48212 Assert.IsFalse (child.IsChildPathOf (child)); } diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/FileServiceTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/FileServiceTests.cs index 132575e471..4235b3c2e7 100644 --- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/FileServiceTests.cs +++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/FileServiceTests.cs @@ -24,6 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+using System;
using System.Collections.Generic;
using System.IO;
using MonoDevelop.Core;
@@ -176,6 +177,33 @@ namespace MonoDevelop.Projects void OnFileChanged (object sender, FileEventArgs e) { fileChangeEvents.Add (e); + }
+
+ [TestCase ("base/child", "base", true)]
+ [TestCase ("/foo", "/foo", true)]
+ [TestCase ("/foo", "/foo", false, ExpectedException = typeof (InvalidOperationException))]
+ [TestCase ("/path/to/child", "/path/to", true)]
+ [TestCase ("base", "other", true, ExpectedException = typeof (InvalidOperationException))]
+ public void CanDeleteFolderWithBaseCheckWorks (string path, string requiredParentDirectory, bool canDeleteParent = true)
+ {
+ FileService.AssertCanDeleteDirectory (path, requiredParentDirectory, canDeleteParent);
+ }
+
+ [Test]
+ [ExpectedException (typeof (InvalidOperationException))]
+ public void CantDeleteSystemFolders ()
+ {
+ FileService.AssertCanDeleteDirectory (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData));
+ FileService.AssertCanDeleteDirectory (Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments));
+ FileService.AssertCanDeleteDirectory (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
+ }
+
+ [Test]
+ [ExpectedException (typeof (InvalidOperationException))]
+ public void CantDeleteRoot ()
+ {
+ string root = Platform.IsWindows ? "C:" : "/";
+ FileService.AssertCanDeleteDirectory (root);
} }
}
|