Improved file serving to support startPaths. Improved code and simplified a few things. Bumped package version to 1.6.7

This commit is contained in:
MattMo 2023-06-29 09:50:12 -07:00
parent 36872164c5
commit 7934f807ef
3 changed files with 109 additions and 91 deletions

View File

@ -46,17 +46,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_File_ShouldWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
resolvedPath.Should().BeEquivalentTo(this.TestFile);
}
[Fact]
public void ServeMultiple_File_WithParentDirectory_ShouldWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "test/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -66,7 +56,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_Directory_ShouldWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeTrue();
@ -76,13 +66,13 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_NavigatingUp_Should_NotWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
}
[Fact]
public void ServeMultiple_Correct_NavigatingUp_Should_Work()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "a/b/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "a/b/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -92,13 +82,13 @@ namespace Rest.Net.Tests
[Fact]
public void ServeMultiple_NavigatingUp_Multiple_Should_NotWork()
{
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "test/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, null, "test/../../test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeFalse();
}
[Fact]
public void ServeSingle_Empty_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -108,7 +98,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_File_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -118,7 +108,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_Directory_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeTrue();
@ -128,7 +118,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_File_Route_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/a/b/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -138,7 +128,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_Directory_Route_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/a/b/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/test2", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeTrue();
@ -148,7 +138,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_File_Route_Invalid_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/a/b/c", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/c", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -158,7 +148,7 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_File_WithoutExtension_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/test", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
@ -168,7 +158,17 @@ namespace Rest.Net.Tests
[Fact]
public void ServeSingle_File_Route_WithoutExtension_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/a/b/c/test", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, null, "/a/b/c/test", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();
resolvedPath.Should().BeEquivalentTo(this.TestFile);
}
[Fact]
public void ServeSingle_File_StartPath_Should_Work()
{
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test", "test/test.html", "index.html", out string resolvedPath, out bool isDirectory).Should().BeTrue();
isDirectory.Should().BeFalse();

View File

@ -508,9 +508,9 @@ namespace MontoyaTech.Rest.Net
/// <param name="compress">Whether or not to compress files served. Default is false.</param>
/// <param name="compressExtensions">A collection of file extensions that should be compressed, example: .jpg, default is null. If and compress is true, all files will be compressed.</param>
/// <returns>The modified response</returns>
public static HttpListenerResponse ServeMultiPage(this HttpListenerResponse response, string basePath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
public static HttpListenerResponse ServeMultiPage(this HttpListenerResponse response, string basePath, string startPath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
{
if (ResolveMultiPagePath(basePath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
if (ResolveMultiPagePath(basePath, startPath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
{
if (isDirectory)
{
@ -537,7 +537,7 @@ namespace MontoyaTech.Rest.Net
}
}
internal static bool ResolveMultiPagePath(string basePath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
internal static bool ResolveMultiPagePath(string basePath, string startPath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
{
resolvedPath = null;
@ -547,55 +547,62 @@ namespace MontoyaTech.Rest.Net
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
requestPath = indexFile;
//See if there is a parent directory in the basePath, if so get it
var parentDirectory = basePath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
//Break the startPath into it's components
var startComponents = startPath?.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//Break the request path into it's components so we can enfore staying in the base path.
var components = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
var requestComponents = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//If the first component is the parent directory, then remove it
if (components.Count > 0 && components[0].Equals(parentDirectory, StringComparison.CurrentCultureIgnoreCase))
components.RemoveAt(0);
//Quirk, if the components is now empty, point to the indexFile
if (components.Count == 0)
components.Add(indexFile);
for (int i = 0; i < components.Count; i++)
//If we have start components, remove request components that match.
if (startComponents != null)
{
if (components[i].Trim() == "..")
for (int i = 0; i < startComponents.Count; i++)
{
components.RemoveAt(i--);
if (i >= 0)
components.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == "...")
{
components.RemoveAt(i--);
if (i >= 0)
components.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
if (i >= 0)
components.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == ".")
{
components.RemoveAt(i--);
if (requestComponents.Count > 0 && requestComponents[0].Equals(startComponents[i], StringComparison.CurrentCultureIgnoreCase))
requestComponents.RemoveAt(0);
}
}
if (components.Count == 0)
//Quirk, if the components is now empty, point to the indexFile
if (requestComponents.Count == 0)
requestComponents.Add(indexFile);
//Process the request components and handle directory changes
for (int i = 0; i < requestComponents.Count; i++)
{
if (requestComponents[i].Trim() == "..")
{
requestComponents.RemoveAt(i--);
if (i >= 0)
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (requestComponents[i].Trim() == "...")
{
requestComponents.RemoveAt(i--);
if (i >= 0)
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
if (i >= 0)
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (requestComponents[i].Trim() == ".")
{
requestComponents.RemoveAt(i--);
}
}
if (requestComponents.Count == 0)
return false;
var absolutePath = Path.Combine(basePath, components.Separate(Path.DirectorySeparatorChar));
var absolutePath = Path.Combine(basePath, requestComponents.Separate(Path.DirectorySeparatorChar));
if (File.Exists(absolutePath))
{
@ -622,14 +629,15 @@ namespace MontoyaTech.Rest.Net
/// </summary>
/// <param name="response">The response to modify</param>
/// <param name="basePath">The base path where to serve files from</param>
/// <param name="startPath">The starting path that should be removed from requests, if null or empty, requests won't be affected</param>
/// <param name="request">The request to serve</param>
/// <param name="indexFile">The name of the index file, default is index.html</param>
/// <param name="compress">Whether or not to compress files served. Default is false.</param>
/// <param name="compressExtensions">A collection of file extensions that should be compressed, example: .jpg, default is null. If and compress is true, all files will be compressed.</param>
/// <returns>The modified response</returns>
public static HttpListenerResponse ServeSinglePage(this HttpListenerResponse response, string basePath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
public static HttpListenerResponse ServeSinglePage(this HttpListenerResponse response, string basePath, string startPath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
{
if (ResolveSinglePagePath(basePath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
if (ResolveSinglePagePath(basePath, startPath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
{
if (isDirectory)
{
@ -656,7 +664,7 @@ namespace MontoyaTech.Rest.Net
}
}
internal static bool ResolveSinglePagePath(string basePath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
internal static bool ResolveSinglePagePath(string basePath, string startPath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
{
resolvedPath = null;
@ -666,72 +674,82 @@ namespace MontoyaTech.Rest.Net
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
requestPath = indexFile;
//See if there is a parent directory in the basePath, if so get it
var parentDirectory = basePath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
//Break the startPath into it's components
var startComponents = startPath?.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//Break the request path into it's components so we can enfore staying in the base path.
var components = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
var requestComponents = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//Process the components and handle any directory navigations.
for (int i = 0; i < components.Count; i++)
//If we have start components, remove request components that match.
if (startComponents != null)
{
if (components[i].Trim() == "..")
for (int i = 0; i < startComponents.Count; i++)
{
components.RemoveAt(i--);
if (requestComponents.Count > 0 && requestComponents[0].Equals(startComponents[i], StringComparison.CurrentCultureIgnoreCase))
requestComponents.RemoveAt(0);
}
}
//Process the request components and handle directory changes
for (int i = 0; i < requestComponents.Count; i++)
{
if (requestComponents[i].Trim() == "..")
{
requestComponents.RemoveAt(i--);
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == "...")
else if (requestComponents[i].Trim() == "...")
{
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
if (i >= 0)
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
else
return false; //Trying to jump outside of basePath
}
else if (components[i].Trim() == ".")
else if (requestComponents[i].Trim() == ".")
{
components.RemoveAt(i--);
requestComponents.RemoveAt(i--);
}
}
//Check the components and remove any that are invalid.
while (components.Count > 0)
while (requestComponents.Count > 0)
{
string path = Path.Combine(basePath, components[0]);
string path = Path.Combine(basePath, requestComponents[0]);
if (File.Exists(path) || Directory.Exists(path))
{
break;
}
//If we have no more components and we are missing an extension and with .html a file exists, then use it.
else if (components.Count == 1 && !components[0].Contains('.') && File.Exists(path + ".html"))
else if (requestComponents.Count == 1 && !requestComponents[0].Contains('.') && File.Exists(path + ".html"))
{
components[0] = components[0] + ".html";
requestComponents[0] = requestComponents[0] + ".html";
break;
}
else
{
components.RemoveAt(0);
requestComponents.RemoveAt(0);
}
}
//Quirk, if the components is now empty, point to the indexFile
if (components.Count == 0)
components.Add(indexFile);
if (requestComponents.Count == 0)
requestComponents.Add(indexFile);
//Combine the path into an absolute path
var absolutePath = Path.Combine(basePath, components.Separate(Path.DirectorySeparatorChar));
var absolutePath = Path.Combine(basePath, requestComponents.Separate(Path.DirectorySeparatorChar));
//If a file exists, return true
if (File.Exists(absolutePath))

View File

@ -17,7 +17,7 @@
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>1.6.6</Version>
<Version>1.6.7</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
</PropertyGroup>