/***************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
This code is licensed under the Visual Studio SDK license terms.
THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
***************************************************************************/
namespace Microsoft.VisualStudio.Package{
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.Build.ComInteropWrapper;
using Microsoft.VisualStudio.Shell.Interop;
using System.Diagnostics;
using Microsoft.VisualStudio.Shell;
using System.IO;
using Microsoft.Win32;
using System.Security;
using System.Globalization;
using System.Windows.Forms.Design;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections;
using System.Security.Permissions;
/// <summary>
/// Does security validation of a project before loading the project
/// </summary>
public class ProjectSecurityChecker : IDisposable
{
#region constants
/// <summary>
/// The dangereous target property.
/// </summary>
internal const string DangerousTargetProperty = "LoadTimeSensitiveTargets";
/// <summary>
/// The dangereous properties property.
/// </summary>
internal const string DangerousPropertyProperty = "LoadTimeSensitiveProperties";
/// <summary>
/// The dangereous items property.
/// </summary>
internal const string DangerousItemsProperty = "LoadTimeSensitiveItems";
/// <summary>
/// The check item locations property.
/// </summary>
internal const string CheckItemLocationProperty = "LoadTimeCheckItemLocation";
/// <summary>
/// The dangereous list item separator.
/// </summary>
internal const string DangerousListSeparator = ";";
/// <summary>
/// The project directory property.
/// </summary>
internal const string ProjectDirectoryProperty = "MSBuildProjectDirectory";
/// <summary>
/// The default dangereous properties.
/// </summary>
internal const string DefaultDangerousProperties = "LoadTimeSensitiveTargets;LoadTimeSensitiveProperties;LoadTimeSensitiveItems;LoadTimeCheckItemLocation;";
/// <summary>
/// The default dangereous targets.
/// </summary>
internal const string DefaultDangerousTargets = "Compile;GetFrameworkPaths;AllProjectOutputGroups;AllProjectOutputGroupsDependencies;CopyRunEnvironmentFiles;ResolveComReferences;ResolveAssemblyReferences;ResolveNativeReferences;";
/// <summary>
/// The default dangereous items.
/// </summary>
internal const string DefaultDangerousItems = ";";
/// <summary>
/// Defined the safe imports subkey in the registry.
/// </summary>
internal const string SafeImportsSubkey = @"MSBuild\SafeImports";
#endregion
#region fields
/// <summary>
/// Defines an object that will be a mutex for this object for synchronizing thread calls.
/// </summary>
private static volatile object Mutex = new object();
/// <summary>
/// Flag determining if the object has been disposed.
/// </summary>
private bool isDisposed;
/// <summary>
/// The associated project shim for the project file
/// </summary>
private ProjectShim projectShim;
/// <summary>
/// The security check helper object used to call out to do necessary security checkings.
/// </summary>
private SecurityCheckHelper securityCheckHelper = new SecurityCheckHelper();
/// <summary>
/// The associated service provider.
/// </summary>
private IServiceProvider serviceProvider;
#endregion
#region properties
/// <summary>
/// The associated project shim for the project file
/// </summary>
/// <devremark>The project shim is made internal in order to be able to be passed to the user project.</devremark>
internal protected ProjectShim ProjectShim
{
get { return this.projectShim; }
}
/// <summary>
/// The security check helper that will be used to perform the necessary checkings.
/// </summary>
protected SecurityCheckHelper SecurityCheckHelper
{
get { return this.securityCheckHelper; }
}
/// <summary>
/// The associated service provider.
/// </summary>
protected IServiceProvider ServiceProvider
{
get
{
return this.serviceProvider;
}
}
#endregion
#region ctors
/// <summary>
/// Overloaded Constructor
/// </summary>
/// <param name="projectFilePath">path to the project file</param>
/// <param name="serviceProvider">A service provider.</param>
public ProjectSecurityChecker(IServiceProvider serviceProvider, string projectFilePath)
{
if (serviceProvider == null)
{
throw new ArgumentNullException("serviceProvider");
}
if (String.IsNullOrEmpty(projectFilePath))
{
throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "projectFilePath");
}
this.serviceProvider = serviceProvider;
// Instantiate a new project shim that we are going to use for security checkings.
EngineShim engine = new EngineShim();
this.projectShim = engine.CreateNewProject();
this.projectShim.Load(projectFilePath);
}
#endregion
#region IDisposable Members
/// <summary>
/// The IDispose interface Dispose method for disposing the object determinastically.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region virtual methods
/// <summary>
/// Check if the project is safe at load/design time
/// </summary>
/// <param name="securityErrorMessage">If the project is not safe contains an error message, describing the reason.</param>
/// <returns>true if the project is safe, false otherwise</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification="The error message needs to be an out parameter. We are following here the Try... method patterns.")]
public virtual bool IsProjectSafeAtLoadTime(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
StringBuilder securityMessageMaker = new StringBuilder() ;
int counter = 0;
string tempMessage;
// STEP 1: Check direct imports.
if (!this.IsProjectSafeWithImports(out tempMessage))
{
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
securityErrorMessage = tempMessage;
}
// STEP 2: Check dangerous properties
if (!this.IsProjectSafeWithProperties(out tempMessage))
{
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
securityErrorMessage = tempMessage;
}
// STEP 3: Check dangerous targets
if (!this.IsProjectSafeWithTargets(out tempMessage))
{
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
securityErrorMessage = tempMessage;
}
// STEP 4: Check dangerous items
if (!this.IsProjectSafeWithItems(out tempMessage))
{
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
securityErrorMessage = tempMessage;
}
// STEP 5: Check UsingTask tasks
if (!this.IsProjectSafeWithUsingTasks(out tempMessage))
{
ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
securityErrorMessage = tempMessage;
}
// STEP 6: Check for items defined within the LoadTimeCheckItemLocation, whether they are defined in safe locations
if (!this.CheckItemsLocation(out tempMessage))
{
securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", (++counter).ToString(CultureInfo.CurrentCulture));
securityMessageMaker.AppendLine(tempMessage);
securityErrorMessage = tempMessage;
}
if (counter > 1)
{
securityErrorMessage = securityMessageMaker.ToString();
}
return String.IsNullOrEmpty(securityErrorMessage);
}
/// <summary>
/// Checks if the project is safe with imports. The project file is considered
/// unsafe if it contains any imports not registered in the safe import regkey.
/// </summary>
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
/// <returns>true if the project is safe regarding imports.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
protected virtual bool IsProjectSafeWithImports(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
// Now get the directly imports and do the comparision.
string[] directImports = this.securityCheckHelper.GetDirectlyImportedProjects(this.projectShim);
if (directImports != null && directImports.Length > 0)
{
IList<string> safeImportList = ProjectSecurityChecker.GetSafeImportList();
for (int i = 0; i < directImports.Length; i++)
{
string fileToCheck = directImports[i];
if (!ProjectSecurityChecker.IsSafeImport(safeImportList, fileToCheck))
{
using (RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
{
securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsImport, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), fileToCheck, Path.Combine(root.Name, SafeImportsSubkey));
}
return false;
}
}
}
return true;
}
/// <summary>
/// Checks if the project is safe regarding properties.
/// </summary>
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
/// <returns>true if the project has only safe properties.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification="The error message needs to be an out parameter. We are following here the Try... method patterns.")]
protected virtual bool IsProjectSafeWithProperties(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
// Now ask the security check heper for the safe properties.
string reasonForFailure;
bool isUserFile;
bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousPropertyProperty,
ProjectSecurityChecker.DefaultDangerousProperties,
this.projectShim,
null,
SecurityCheckPass.Properties,
out reasonForFailure,
out isUserFile);
if (!isProjectSafe)
{
securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsProperty);
}
return isProjectSafe;
}
/// <summary>
/// Checks if the project is safe regarding targets.
/// </summary>
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
/// <returns>true if the project has only safe targets.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
protected virtual bool IsProjectSafeWithTargets(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
// Now ask the security check heper for the safe targets.
string reasonForFailure;
bool isUserFile;
bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousTargetProperty,
ProjectSecurityChecker.DefaultDangerousTargets,
this.projectShim,
null,
SecurityCheckPass.Targets,
out reasonForFailure,
out isUserFile);
if (!isProjectSafe)
{
securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsTarget);
}
return isProjectSafe;
}
/// <summary>
/// Checks if the project is safe regarding items.
/// </summary>
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
/// <returns>true if the project has only safe items.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification="The error message needs to be an out parameter. We are following here the Try... method patterns.")]
protected virtual bool IsProjectSafeWithItems(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
// Now ask the security check heper for the safe items.
string reasonForFailure;
bool isUserFile;
bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousItemsProperty,
ProjectSecurityChecker.DefaultDangerousItems,
this.projectShim,
null,
SecurityCheckPass.Items,
out reasonForFailure,
out isUserFile);
if (!isProjectSafe)
{
securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsItem);
}
return isProjectSafe;
}
/// <summary>
/// Checks if the project is safe with using tasks.
/// </summary>
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
/// <returns>true if the project has no using tasks defined in the project file.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
protected virtual bool IsProjectSafeWithUsingTasks(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
string[] usingTasks = this.securityCheckHelper.GetNonImportedUsingTasks(this.projectShim);
if (usingTasks != null && usingTasks.Length > 0)
{
securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsUsingTask, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), usingTasks[0]);
return false;
}
return true;
}
/// <summary>
/// If the project contains the LoadTimeCheckItemsWithinProjectCone property, the method verifies that all the items listed in there are within the project cone.
/// Also checks that the project is not in Program Files or Windows if the property was there.
/// </summary>
/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
/// <returns>true if the project has no badly defined project items.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
Justification="The error message needs to be an out parameter. We are following here the Try... method patterns.")]
protected virtual bool CheckItemsLocation(out string securityErrorMessage)
{
securityErrorMessage = String.Empty;
// Get the <LoadTimeCheckItemLocation> property from the project
string itemLocationProperty = this.projectShim.GetEvaluatedProperty(ProjectSecurityChecker.CheckItemLocationProperty);
if (String.IsNullOrEmpty(itemLocationProperty))
{
return true;
}
// Takes a semicolon separated list of entries, splits them and puts them into a list with values trimmed.
string[] items = itemLocationProperty.Split(ProjectSecurityChecker.DangerousListSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
IList<string> itemsToCheck = new List<string>();
foreach (string item in items)
{
itemsToCheck.Add(item.Trim());
}
// Now check the items for being defined in a safe location.
string reasonForFailure;
ItemSecurityChecker itemsSecurityChecker = new ItemSecurityChecker(this.serviceProvider, this.projectShim.FullFileName);
if (!itemsSecurityChecker.CheckItemsSecurity(this.projectShim, itemsToCheck, out reasonForFailure))
{
securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsItemLocation, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), reasonForFailure);
return false;
}
return true;
}
/// <summary>
/// The method that does the cleanup.
/// </summary>
/// <param name="disposing">true if called from IDispose.Dispose; false if called from Finalizer.</param>
protected virtual void Dispose(bool disposing)
{
// Everybody can go here.
if (!this.isDisposed)
{
// Synchronize calls to the Dispose simultaniously.
lock (Mutex)
{
if (disposing)
{
this.projectShim.ParentEngine.UnloadProject(this.projectShim);
}
this.isDisposed = true;
}
}
}
#endregion
#region helper methods
/// <summary>
/// Gets a message string that has an associated format with a reason for failure.
/// </summary>
/// <param name="reasonForFailure"></param>
/// <param name="resourceID"></param>
/// <returns></returns>
internal string GetMessageString(string reasonForFailure, string resourceID)
{
Debug.Assert(!String.IsNullOrEmpty(reasonForFailure), "The reason for failure should not be empty or null");
Debug.Assert(!String.IsNullOrEmpty(resourceID), "The resource id string cannot be empty");
return String.Format(CultureInfo.CurrentCulture, SR.GetString(resourceID, Path.GetFileName(this.projectShim.FullFileName), reasonForFailure));
}
/// <summary>
/// Generates a format string that will be pushed to the More Detailed dialog.
/// </summary>
/// <param name="securityMessageMaker">The Stringbuilder object containing the formatted message.</param>
/// <param name="counter">The 'issue' number.</param>
/// <param name="securityErrorMessage">The message to format.</param>
private static void FormatMessage(StringBuilder securityMessageMaker, int counter, string securityErrorMessage)
{
securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", counter.ToString(CultureInfo.CurrentCulture));
securityMessageMaker.AppendLine(securityErrorMessage);
securityMessageMaker.Append(Environment.NewLine);
}
/// <summary>
/// Returns a set of file info's describing the files in the SafeImports registry location.
/// </summary>
/// <returns>A set of FileInfo objects describing the files in the SafeImports location.</returns>
private static IList<string> GetSafeImportList()
{
List<string> importsList = new List<string>();
using (RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
{
if (root != null)
{
using (RegistryKey key = root.OpenSubKey(SafeImportsSubkey))
{
if (key != null)
{
foreach (string value in key.GetValueNames())
{
string keyValue = key.GetValue(value, String.Empty, RegistryValueOptions.None) as string;
// Make sure that the environment variables are expanded.
keyValue = System.Environment.ExpandEnvironmentVariables(keyValue);
Uri uri;
if (!String.IsNullOrEmpty(keyValue) && Uri.TryCreate(keyValue, UriKind.Absolute, out uri) && uri.IsAbsoluteUri)
{
importsList.Add(keyValue);
}
}
}
}
}
}
return importsList;
}
/// <summary>
/// Checks if an import is a safe import.
/// </summary>
/// <param name="safeImportsList">A list of safe imports from teh registry.</param>
/// <param name="fileToCheck">The file to check.</param>
/// <returns>true if the file to check can be found in the safe import list</returns>
private static bool IsSafeImport(IList<string> safeImportsList, string fileToCheck)
{
foreach (string safeImport in safeImportsList)
{
if (NativeMethods.IsSamePath(safeImport, fileToCheck))
{
return true;
}
}
return false;
}
#endregion
#region nested types
/// <summary>
/// Class for checking that the items defined in LoadTimeCheckItemLocation are being defined in safe locations.
/// </summary>
private class ItemSecurityChecker
{
#region fields
/// <summary>
/// The associated service provider.
/// </summary>
private IServiceProvider serviceProvider;
/// <summary>
/// The solutionFolder;
/// </summary>
private Uri solutionFolder;
/// <summary>
/// The project folder
/// </summary>
private Uri projectFolder;
/// <summary>
/// The set of special folders.
/// </summary>
private IList<Uri> specialFolders;
#endregion
#region ctors
/// <summary>
/// Overloaded Constructor
/// </summary>
/// <param name="projectFilePath">path to the project file</param>
/// <param name="serviceProvider">A service provider.</param>
internal ItemSecurityChecker(IServiceProvider serviceProvider, string projectFullPath)
{
this.serviceProvider = serviceProvider;
// Initialize the project and solution folders.
this.SetProjectFolder(projectFullPath);
this.SetSolutionFolder();
// Set the special folders. Maybe this should be a static.
this.specialFolders = ItemSecurityChecker.SetSpecialFolders();
}
#endregion
#region methods
/// <summary>
/// Checks whether a set of project items described by the LoadTimeCheckItemLocation are in a safe location.
/// </summary>
/// <param name="projectShim">The project shim containing the items to be checked.</param>
/// <param name="itemsToCheck">The list of items to check if they are in the project cone.</param>
/// <param name="reasonForFailure">The reason for failure if any of the files fails</param>
/// <returns>true if all project items are in the project cone. Otherwise false.</returns>
internal bool CheckItemsSecurity(ProjectShim projectShim, IList<string> itemsToCheck, out string reasonForFailure)
{
reasonForFailure = String.Empty;
// If nothing to check assume that everything is ok.
if (itemsToCheck == null)
{
return true;
}
Debug.Assert(projectShim != null, "Cannot check the items if no project has been defined!");
foreach (string itemName in itemsToCheck)
{
BuildItemGroupShim group = projectShim.GetEvaluatedItemsByNameIgnoringCondition(itemName);
if (group != null)
{
IEnumerator enumerator = group.GetEnumerator();
while (enumerator.MoveNext())
{
BuildItemShim item = enumerator.Current as BuildItemShim;
string finalItem = item.FinalItemSpec;
if (!String.IsNullOrEmpty(finalItem))
{
// Perform the actual check - start with normalizing the path. Relative paths
// should be treated as relative to the project file.
string fullPath = this.GetFullPath(finalItem);
// If the fullpath of the item is suspiciously short do not check it.
if (fullPath.Length >= 3)
{
Uri uri = null;
// If we cannot create a uri from the item path return with the error
if (!Uri.TryCreate(fullPath, UriKind.Absolute, out uri))
{
reasonForFailure = fullPath;
return false;
}
// Check if the item points to a network share
if (uri.IsUnc)
{
reasonForFailure = fullPath;
return false;
}
// Check if the item is located in a drive root directory
if (uri.Segments.Length == 3 && uri.Segments[1] == ":" && uri.Segments[2][0] == Path.DirectorySeparatorChar)
{
reasonForFailure = fullPath;
return false;
}
//Check if the item is not in a special folder.
foreach (Uri specialFolder in this.specialFolders)
{
if (ItemSecurityChecker.IsItemInCone(uri, specialFolder))
{
reasonForFailure = fullPath;
return false;
}
}
}
else
{
reasonForFailure = fullPath;
return false;
}
}
}
}
}
return true;
}
/// <summary>
/// Gets the list of special directories. This method should be optimized if called more then once.
/// </summary>
/// <returns>The list of special directories</returns>
private static IList<Uri> SetSpecialFolders()
{
string[] specialFolderArray = new string[5]
{
Environment.GetFolderPath(Environment.SpecialFolder.System),
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Environment.GetFolderPath(Environment.SpecialFolder.Startup),
ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.Windows),
ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.CommonStartup)
};
List<Uri> specialFolders = new List<Uri>(5);
// Add trailing backslash to the folders.
foreach (string specialFolder in specialFolderArray)
{
string tempFolder = specialFolder;
if (!tempFolder.EndsWith("\\", StringComparison.Ordinal))
{
tempFolder += "\\";
}
specialFolders.Add(new Uri(tempFolder));
}
return specialFolders;
}
/// <summary>
/// Some special folders are not supported by System.Environment.GetFolderPath. Get these special folders using p/invoke.
/// </summary>
/// <param name="specialFolder">The type of special folder to retrieve.</param>
/// <returns>The folder path</returns>
private static string GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder extendedSpecialFolder)
{
string specialFolder = null;
IntPtr buffer = IntPtr.Zero;
// Demand Unmanaged code permission. It should be normal to demand UnmanagedCodePermission from an assembly integrating into VS.
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
try
{
buffer = Marshal.AllocHGlobal((NativeMethods.MAX_PATH + 1) * 2);
IntPtr[] pathIdentifier = new IntPtr[1];
if (ErrorHandler.Succeeded(UnsafeNativeMethods.SHGetSpecialFolderLocation(IntPtr.Zero, (int)extendedSpecialFolder, pathIdentifier)) && UnsafeNativeMethods.SHGetPathFromIDList(pathIdentifier[0], buffer))
{
specialFolder = Marshal.PtrToStringAuto(buffer);
}
}
finally
{
if (buffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(buffer);
}
}
return specialFolder;
}
/// <summary>
/// Checks if the itemToCheck is in the cone of the baseUri.
/// </summary>
/// <param name="itemToCheck">The item to check</param>
/// <param name="baseUri">The base to the item. This should define a folder.</param>
/// <returns>true if the item to check is in the cone of the baseUri.</returns>
private static bool IsItemInCone(Uri itemToCheck, Uri baseUri)
{
Debug.Assert(itemToCheck != null && baseUri != null, "Cannot check for items since the input is wrong");
Debug.Assert(!NativeMethods.IsSamePath(Path.GetDirectoryName(baseUri.LocalPath), baseUri.LocalPath), "The " + baseUri.LocalPath + " is not a folder!");
return (itemToCheck.IsFile && baseUri.IsFile &&
String.Compare(itemToCheck.LocalPath, 0, baseUri.LocalPath, 0, baseUri.LocalPath.Length, StringComparison.OrdinalIgnoreCase) == 0);
}
/// <summary>
/// Sets the solution folder.
/// </summary>
private void SetSolutionFolder()
{
if (this.solutionFolder != null)
{
return;
}
IVsSolution solution = this.serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
Debug.Assert(solution != null, "Could not retrieve the solution service from the global service provider");
string solutionDirectory, solutionFile, userOptionsFile;
// We do not want to throw. If we cannot set the solution related constants we set them to empty string.
ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out solutionDirectory, out solutionFile, out userOptionsFile));
if (String.IsNullOrEmpty(solutionDirectory))
{
return;
}
// Make sure the solution dir ends with a backslash
if (solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
{
solutionDirectory += Path.DirectorySeparatorChar;
}
Uri.TryCreate(solutionDirectory, UriKind.Absolute, out this.solutionFolder);
Debug.Assert(this.solutionFolder != null, "Could not create the Uri for the solution folder");
}
/// <summary>
/// Sets the project folder.
/// </summary>
/// <param name="projectFullPath">The path to the project</param>
private void SetProjectFolder(string projectFullPath)
{
if (this.projectFolder != null)
{
return;
}
string tempProjectFolder = Path.GetDirectoryName(projectFullPath);
// Make sure the project dir ends with a backslash
if (!tempProjectFolder.EndsWith("\\", StringComparison.Ordinal) && !tempProjectFolder.EndsWith("/", StringComparison.Ordinal))
{
tempProjectFolder += "\\";
}
Uri.TryCreate(tempProjectFolder, UriKind.Absolute, out this.projectFolder);
Debug.Assert(this.projectFolder != null, "Could not create the Uri for the project folder");
}
/// <summary>
/// Gets the fullpath of an item.
/// Relative pathes are treated as relative to the project file.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The ful path of the item.</returns>
private string GetFullPath(string item)
{
Url url;
if (Path.IsPathRooted(item))
{
// Use absolute path
url = new Microsoft.VisualStudio.Shell.Url(item);
}
else
{
// Path is relative, so make it relative to project path
url = new Url(new Url(this.projectFolder.LocalPath), item);
}
return url.AbsoluteUrl;
}
#endregion
}
#endregion
}
}
|