The 'api/files?physical_path={value}' endpoint now returns a single resource. Added better support for physical paths using the alternate directory separator character.

This commit is contained in:
Jimmy Campbell 2016-12-20 18:35:41 -08:00
Родитель 658f98cf5e
Коммит a1a5b1dd76
12 изменённых файлов: 175 добавлений и 153 удалений

Просмотреть файл

@ -39,12 +39,11 @@ namespace Microsoft.IIS.Administration.Files
public IEnumerable<string> GetClaims(string path) public IEnumerable<string> GetClaims(string path)
{ {
var absolutePath = PathUtil.GetFullPath(path);
var claims = new List<string>(); var claims = new List<string>();
// //
// Path must be absolute with no environment variables // Path must be absolute with no environment variables
if (!absolutePath.Equals(path, StringComparison.OrdinalIgnoreCase)) { if (!PathUtil.IsFullPath(path)) {
return claims; return claims;
} }
@ -52,7 +51,7 @@ namespace Microsoft.IIS.Administration.Files
// Best match // Best match
foreach (var location in Options.Locations) { foreach (var location in Options.Locations) {
if (PathUtil.PathStartsWith(absolutePath, location.Path)) { if (HasPrefix(path, location.Path)) {
claims = location.Claims; claims = location.Claims;
@ -62,5 +61,35 @@ namespace Microsoft.IIS.Administration.Files
return claims; return claims;
} }
private static bool HasPrefix(string path, string prefix)
{
//
// Remove trailing separator (if any) from prefix
if (prefix.Length > 0 && prefix.LastIndexOfAny(PathUtil.SEPARATORS) == prefix.Length - 1) {
prefix = prefix.Substring(0, prefix.Length - 1);
}
if (prefix.Length > path.Length) {
return false;
}
var testParts = path.Split(PathUtil.SEPARATORS);
var prefixParts = prefix.Split(PathUtil.SEPARATORS);
if (prefixParts.Length > testParts.Length) {
return false;
}
for (var i = 0; i < prefixParts.Length; i++) {
if (!prefixParts[i].Equals(testParts[i], StringComparison.OrdinalIgnoreCase)) {
return false;
}
}
return true;
}
} }
} }

Просмотреть файл

@ -16,6 +16,9 @@ namespace Microsoft.IIS.Administration.Files
public class FilesController : ApiBaseController public class FilesController : ApiBaseController
{ {
private const string nameKey = "name";
private const string physicalPathKey = "physical_path";
IFileOptions _options; IFileOptions _options;
IFileProvider _provider = FileProvider.Default; IFileProvider _provider = FileProvider.Default;
@ -29,18 +32,22 @@ namespace Microsoft.IIS.Administration.Files
public object Get() public object Get()
{ {
string parentUuid = Context.Request.Query[Defines.PARENT_IDENTIFIER]; string parentUuid = Context.Request.Query[Defines.PARENT_IDENTIFIER];
string name = Context.Request.Query["name"]; string name = Context.Request.Query[nameKey];
string physicalPath = Context.Request.Query["physical_path"]; string physicalPath = Context.Request.Query[physicalPathKey];
var files = new List<object>(); var files = new List<object>();
Fields fields = Context.Request.GetFields(); Fields fields = Context.Request.GetFields();
if (!string.IsNullOrEmpty(name)) { if (!string.IsNullOrEmpty(name)) {
if (name.IndexOfAny(PathUtil.InvalidFileNameChars) != -1) { if (name.IndexOfAny(PathUtil.InvalidFileNameChars) != -1) {
throw new ApiArgumentException("name"); throw new ApiArgumentException(nameKey);
} }
} }
if (physicalPath != null) {
return GetByPhysicalPath(physicalPath, fields);
}
if (parentUuid != null) { if (parentUuid != null) {
var fileId = FileId.FromUuid(parentUuid); var fileId = FileId.FromUuid(parentUuid);
@ -50,9 +57,6 @@ namespace Microsoft.IIS.Administration.Files
FillWithChildren(files, fileId.PhysicalPath, $"*{name}*", fields); FillWithChildren(files, fileId.PhysicalPath, $"*{name}*", fields);
} }
else if (!string.IsNullOrEmpty(physicalPath)) {
FillFromPhysicalPath(files, physicalPath, fields);
}
else { else {
FillFromLocations(files, name, fields); FillFromLocations(files, name, fields);
} }
@ -204,6 +208,41 @@ namespace Microsoft.IIS.Administration.Files
private object GetByPhysicalPath(string physicalPath, Fields fields)
{
if (string.IsNullOrEmpty(physicalPath)) {
throw new ApiArgumentException(physicalPathKey);
}
physicalPath = System.Environment.ExpandEnvironmentVariables(physicalPath);
if (!PathUtil.IsPathRooted(physicalPath)) {
throw new ApiArgumentException(physicalPathKey);
}
try {
physicalPath = PathUtil.GetFullPath(physicalPath);
}
catch (ArgumentException) {
throw new ApiArgumentException(physicalPathKey);
}
object model = null;
if (_provider.FileExists(physicalPath)) {
model = FilesHelper.FileToJsonModel(_provider.GetFileInfo(physicalPath), fields);
}
else if (_provider.DirectoryExists(physicalPath)) {
model = FilesHelper.DirectoryToJsonModel(_provider.GetDirectoryInfo(physicalPath), fields);
}
if (model == null) {
return NotFound();
}
return model;
}
private void FillWithChildren(List<object> models, string physicalPath, string nameFilter, Fields fields) private void FillWithChildren(List<object> models, string physicalPath, string nameFilter, Fields fields)
{ {
// //
@ -219,16 +258,6 @@ namespace Microsoft.IIS.Administration.Files
} }
} }
private void FillFromPhysicalPath(List<object> models, string physicalPath, Fields fields)
{
if (_provider.FileExists(physicalPath)) {
models.Add(FilesHelper.FileToJsonModelRef(_provider.GetFileInfo(physicalPath), fields));
}
else if (_provider.DirectoryExists(physicalPath)) {
models.Add(FilesHelper.DirectoryToJsonModelRef(_provider.GetDirectoryInfo(physicalPath), fields));
}
}
private void FillFromLocations(List<object> models, string nameFilter, Fields fields) private void FillFromLocations(List<object> models, string nameFilter, Fields fields)
{ {
foreach (var location in _options.Locations) { foreach (var location in _options.Locations) {

Просмотреть файл

@ -26,7 +26,7 @@ namespace Microsoft.IIS.Administration.Files
var expanded = Environment.ExpandEnvironmentVariables(path); var expanded = Environment.ExpandEnvironmentVariables(path);
if (!Path.IsPathRooted(expanded)) { if (!IsPathRooted(expanded)) {
throw new ArgumentException("Path must be rooted.", nameof(path)); throw new ArgumentException("Path must be rooted.", nameof(path));
} }
@ -43,74 +43,45 @@ namespace Microsoft.IIS.Administration.Files
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
//
// Expand to make sure the path is rooted before calling GetFullPath
var expanded = Environment.ExpandEnvironmentVariables(path); var expanded = Environment.ExpandEnvironmentVariables(path);
if (!Path.IsPathRooted(expanded)) { if (!IsPathRooted(expanded)) {
return false; return false;
} }
return path.Equals(Path.GetFullPath(expanded), StringComparison.OrdinalIgnoreCase); bool ret = false;
try {
ret = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
.Equals(Path.GetFullPath(expanded), StringComparison.OrdinalIgnoreCase);
}
catch (ArgumentException) {
//
// Argument exception for invalid paths such as '////' (Invalid network share format)
return false;
} }
public static int PrefixSegments(string prefix, string path, StringComparison stringComparison = StringComparison.OrdinalIgnoreCase) return ret;
{
if (prefix == null) {
throw new ArgumentNullException(nameof(prefix));
} }
if (path == null) {
public static bool IsPathRooted(string path)
{
if (string.IsNullOrEmpty(path)) {
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
if (!Path.IsPathRooted(prefix) || !Path.IsPathRooted(path)) {
throw new ArgumentException("Paths must be rooted."); if (!Path.IsPathRooted(path)) {
return false;
} }
var prefixParts = prefix.TrimEnd(SEPARATORS).Split(SEPARATORS); // Prevent cases such as 'c:'
var pathParts = path.TrimEnd(SEPARATORS).Split(SEPARATORS); if (path.IndexOfAny(SEPARATORS) == -1) {
return false;
if (prefixParts.Length > pathParts.Length) {
return -1;
} }
int index = 0;
while (pathParts.Length > index && prefixParts.Length > index && prefixParts[index].Equals(pathParts[index], stringComparison)) {
index++;
}
if (prefixParts.Length > index) {
return -1;
}
return index == 0 ? -1 : index;
}
public static bool IsParentPath(string parent, string child)
{
var prefixSegments = PathUtil.PrefixSegments(parent, child);
if (prefixSegments > 0 && NumberOfSegments(child) > NumberOfSegments(parent)) {
return true; return true;
} }
return false;
}
public static int NumberOfSegments(string path)
{
if (path == null) {
throw new ArgumentNullException(nameof(path));
}
if (!path.StartsWith("/")) {
throw new ArgumentException("Path must begin with '/'.");
}
return path.TrimEnd(SEPARATORS).Split(SEPARATORS).Length;
}
public static string TrimStart(this string val, string prefix, StringComparison stringComparision = StringComparison.Ordinal)
{
if (val.StartsWith(prefix, stringComparision)) {
val = val.Remove(0, prefix.Length);
}
return val;
}
public static string RemoveLastSegment(string path) public static string RemoveLastSegment(string path)
{ {
@ -127,37 +98,6 @@ namespace Microsoft.IIS.Administration.Files
return ret == "/" ? ret : ret.TrimEnd('/'); return ret == "/" ? ret : ret.TrimEnd('/');
} }
public static bool PathStartsWith(string path, string prefix)
{
if (string.IsNullOrEmpty(path)) {
throw new ArgumentNullException(nameof(path));
}
if (string.IsNullOrEmpty(prefix)) {
throw new ArgumentNullException(nameof(prefix));
}
if (prefix.Length > path.Length) {
return false;
}
var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
var testParts = path.Split(separators);
var prefixParts = prefix.TrimEnd(separators).Split(separators);
if (prefixParts.Length > testParts.Length) {
return false;
}
for (var i = 0; i < prefixParts.Length; i++) {
if (!prefixParts[i].Equals(testParts[i], StringComparison.OrdinalIgnoreCase)) {
return false;
}
}
return true;
}
public static bool IsValidFileName(string name) public static bool IsValidFileName(string name)
{ {
return !string.IsNullOrEmpty(name) && return !string.IsNullOrEmpty(name) &&

Просмотреть файл

@ -280,6 +280,7 @@ namespace Microsoft.IIS.Administration.WebServer.Applications
if (physicalPath != null) { if (physicalPath != null) {
physicalPath = physicalPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath); var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {
@ -294,7 +295,7 @@ namespace Microsoft.IIS.Administration.WebServer.Applications
var rootVDir = app.VirtualDirectories["/"]; var rootVDir = app.VirtualDirectories["/"];
if (rootVDir != null) { if (rootVDir != null) {
rootVDir.PhysicalPath = physicalPath.Replace('/', '\\'); rootVDir.PhysicalPath = physicalPath;
} }
} }

Просмотреть файл

@ -25,6 +25,7 @@ namespace Microsoft.IIS.Administration.WebServer.Compression
DynamicHelper.If((object) model.directory, v => { DynamicHelper.If((object) model.directory, v => {
v = v.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(v); var expanded = System.Environment.ExpandEnvironmentVariables(v);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {

Просмотреть файл

@ -314,7 +314,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
Application parentApp = null; Application parentApp = null;
var maxMatch = 0; var maxMatch = 0;
foreach (var app in site.Applications) { foreach (var app in site.Applications) {
var matchingPrefix = PathUtil.PrefixSegments(app.Path, path); var matchingPrefix = PrefixSegments(app.Path, path);
if (matchingPrefix > maxMatch) { if (matchingPrefix > maxMatch) {
parentApp = app; parentApp = app;
maxMatch = matchingPrefix; maxMatch = matchingPrefix;
@ -339,7 +339,7 @@ namespace Microsoft.IIS.Administration.WebServer.Files
var testPath = path.TrimStart(parentApp.Path.TrimEnd('/'), StringComparison.OrdinalIgnoreCase); var testPath = path.TrimStart(parentApp.Path.TrimEnd('/'), StringComparison.OrdinalIgnoreCase);
testPath = testPath == string.Empty ? "/" : testPath; testPath = testPath == string.Empty ? "/" : testPath;
foreach (var vdir in parentApp.VirtualDirectories) { foreach (var vdir in parentApp.VirtualDirectories) {
var matchingPrefix = PathUtil.PrefixSegments(vdir.Path, testPath); var matchingPrefix = PrefixSegments(vdir.Path, testPath);
if (matchingPrefix > maxMatch) { if (matchingPrefix > maxMatch) {
parentVdir = vdir; parentVdir = vdir;
maxMatch = matchingPrefix; maxMatch = matchingPrefix;
@ -389,7 +389,17 @@ namespace Microsoft.IIS.Administration.WebServer.Files
} }
} }
var absolute = PathUtil.GetFullPath(path); string absolute = null;
try {
absolute = PathUtil.GetFullPath(path);
}
catch (ArgumentException) {
//
// Argument exception for invalid paths such as '////' (Invalid network share format)
return false;
}
var slashIndex = absolute.IndexOf(Path.DirectorySeparatorChar); var slashIndex = absolute.IndexOf(Path.DirectorySeparatorChar);
if (!absolute.Substring(slashIndex, absolute.Length - slashIndex).Equals(path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))) { if (!absolute.Substring(slashIndex, absolute.Length - slashIndex).Equals(path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))) {
return false; return false;
@ -482,5 +492,44 @@ namespace Microsoft.IIS.Administration.WebServer.Files
return ret; return ret;
} }
private static int PrefixSegments(string prefix, string path, StringComparison stringComparison = StringComparison.OrdinalIgnoreCase)
{
if (prefix == null) {
throw new ArgumentNullException(nameof(prefix));
}
if (path == null) {
throw new ArgumentNullException(nameof(path));
}
if (!PathUtil.IsPathRooted(prefix) || !PathUtil.IsPathRooted(path)) {
throw new ArgumentException("Paths must be rooted.");
}
var prefixParts = prefix.TrimEnd(PathUtil.SEPARATORS).Split(PathUtil.SEPARATORS);
var pathParts = path.TrimEnd(PathUtil.SEPARATORS).Split(PathUtil.SEPARATORS);
if (prefixParts.Length > pathParts.Length) {
return -1;
}
int index = 0;
while (pathParts.Length > index && prefixParts.Length > index && prefixParts[index].Equals(pathParts[index], stringComparison)) {
index++;
}
if (prefixParts.Length > index) {
return -1;
}
return index == 0 ? -1 : index;
}
private static string TrimStart(this string val, string prefix, StringComparison stringComparision = StringComparison.Ordinal)
{
if (val.StartsWith(prefix, stringComparision)) {
val = val.Remove(0, prefix.Length);
}
return val;
}
} }
} }

Просмотреть файл

@ -84,6 +84,7 @@ namespace Microsoft.IIS.Administration.WebServer.HttpRequestTracing
DynamicHelper.If((object)model.directory, v => { DynamicHelper.If((object)model.directory, v => {
v = v.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(v); var expanded = System.Environment.ExpandEnvironmentVariables(v);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {

Просмотреть файл

@ -238,7 +238,7 @@ namespace Microsoft.IIS.Administration.WebServer.Logging
CentralBinaryLogFile bFile = logSection.CentralBinaryLogFile; CentralBinaryLogFile bFile = logSection.CentralBinaryLogFile;
DynamicHelper.If((object)bSettings.directory, v => { DynamicHelper.If((object)bSettings.directory, v => {
EnsureCanUseDirectory(v); EnsureCanUseDirectory(ref v);
bFile.Directory = v; bFile.Directory = v;
}); });
@ -256,7 +256,7 @@ namespace Microsoft.IIS.Administration.WebServer.Logging
CentralW3CLogFile wFile = logSection.CentralW3CLogFile; CentralW3CLogFile wFile = logSection.CentralW3CLogFile;
DynamicHelper.If((object)wSettings.directory, v => { DynamicHelper.If((object)wSettings.directory, v => {
EnsureCanUseDirectory(v); EnsureCanUseDirectory(ref v);
wFile.Directory = v; wFile.Directory = v;
}); });
@ -310,7 +310,7 @@ namespace Microsoft.IIS.Administration.WebServer.Logging
dynamic siteSettings = model; dynamic siteSettings = model;
DynamicHelper.If((object)siteSettings.directory, v => { DynamicHelper.If((object)siteSettings.directory, v => {
EnsureCanUseDirectory(v); EnsureCanUseDirectory(ref v);
siteLogFile.Directory = v; siteLogFile.Directory = v;
}); });
@ -515,8 +515,9 @@ namespace Microsoft.IIS.Administration.WebServer.Logging
private static void EnsureCanUseDirectory(string path) private static void EnsureCanUseDirectory(ref string path)
{ {
path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(path); var expanded = System.Environment.ExpandEnvironmentVariables(path);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {

Просмотреть файл

@ -36,7 +36,7 @@ namespace Microsoft.IIS.Administration.WebServer.Modules
throw new ApiArgumentException("image"); throw new ApiArgumentException("image");
} }
AssertCanUseImage(image); AssertCanUseImage(ref image);
var globalCollection = GetGlobalModulesCollection(); var globalCollection = GetGlobalModulesCollection();
@ -294,7 +294,7 @@ namespace Microsoft.IIS.Administration.WebServer.Modules
string image = DynamicHelper.Value(model.image); string image = DynamicHelper.Value(model.image);
if (image != null) { if (image != null) {
AssertCanUseImage(image); AssertCanUseImage(ref image);
globalModule.Image = image; globalModule.Image = image;
} }
@ -552,8 +552,9 @@ namespace Microsoft.IIS.Administration.WebServer.Modules
private static void AssertCanUseImage(string path) private static void AssertCanUseImage(ref string path)
{ {
path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(path); var expanded = System.Environment.ExpandEnvironmentVariables(path);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {

Просмотреть файл

@ -377,6 +377,7 @@ namespace Microsoft.IIS.Administration.WebServer.Sites
if(physicalPath != null) { if(physicalPath != null) {
physicalPath = physicalPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath); var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {
@ -396,7 +397,7 @@ namespace Microsoft.IIS.Administration.WebServer.Sites
if (rootVDir != null) { if (rootVDir != null) {
rootVDir.PhysicalPath = physicalPath.Replace('/', '\\'); rootVDir.PhysicalPath = physicalPath;
} }
} }
} }

Просмотреть файл

@ -193,6 +193,7 @@ namespace Microsoft.IIS.Administration.WebServer.VirtualDirectories
if(physicalPath != null) { if(physicalPath != null) {
physicalPath = physicalPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath); var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath);
if (!PathUtil.IsFullPath(expanded)) { if (!PathUtil.IsFullPath(expanded)) {
@ -205,7 +206,7 @@ namespace Microsoft.IIS.Administration.WebServer.VirtualDirectories
throw new NotFoundException("physical_path"); throw new NotFoundException("physical_path");
} }
vdir.PhysicalPath = physicalPath.Replace('/', '\\'); vdir.PhysicalPath = physicalPath;
} }
if (model.identity != null) { if (model.identity != null) {

Просмотреть файл

@ -92,15 +92,6 @@ namespace Microsoft.IIS.Administration.Tests
} }
} }
[Fact]
public void IsParentPath()
{
Assert.True(PathUtil.IsParentPath("/", "/a"));
Assert.True(PathUtil.IsParentPath("/a/b", "/a/b/c"));
Assert.False(PathUtil.IsParentPath("/", "/"));
Assert.False(PathUtil.IsParentPath("/a/b", "/a/bc"));
}
[Fact] [Fact]
public void IsExactVdirPath() public void IsExactVdirPath()
{ {
@ -221,29 +212,6 @@ namespace Microsoft.IIS.Administration.Tests
} }
} }
[Fact]
public void PrefixSegments()
{
var path = "/";
Assert.Equal(PathUtil.PrefixSegments("/", path), 1);
Assert.Equal(PathUtil.PrefixSegments("/a", path), -1);
path = "/abc/def/ghi";
Assert.Equal(PathUtil.PrefixSegments("/", path), 1);
Assert.Equal(PathUtil.PrefixSegments("/abc", path), 2);
Assert.Equal(PathUtil.PrefixSegments("/abc/", path), 2);
Assert.Equal(PathUtil.PrefixSegments("/abcd/", path), -1);
Assert.Equal(PathUtil.PrefixSegments("/aBc/", path), 2);
Assert.Equal(PathUtil.PrefixSegments("/aBc/", path, StringComparison.Ordinal), -1);
Assert.Equal(PathUtil.PrefixSegments("/abc/def", path), 3);
Assert.Equal(PathUtil.PrefixSegments("/abc/def/", path), 3);
Assert.Equal(PathUtil.PrefixSegments("/abc/defg/", path), -1);
Assert.Equal(PathUtil.PrefixSegments("/abca/def/", path), -1);
}
[Fact] [Fact]
public void RemoveLastSegment() public void RemoveLastSegment()
{ {