Bumped package version to 1.6.3. Added unit tests for serving files. Fixed issues and improved the security of the serve file extensions and the ability to compress files.
This commit is contained in:
parent
d96c44e542
commit
c2f60ff19f
@ -12,29 +12,98 @@ namespace Rest.Net.Tests
|
||||
{
|
||||
public class ServeFileTests
|
||||
{
|
||||
public string BaseDirectory = null;
|
||||
|
||||
public string TestDirectory = null;
|
||||
|
||||
public string TestFile = null;
|
||||
|
||||
public ServeFileTests()
|
||||
{
|
||||
this.TestDirectory = Path.Combine(Environment.CurrentDirectory, "test/");
|
||||
this.BaseDirectory = Path.Combine(Environment.CurrentDirectory, "test");
|
||||
|
||||
if (!Directory.Exists(this.BaseDirectory))
|
||||
Directory.CreateDirectory(this.BaseDirectory);
|
||||
|
||||
this.TestDirectory = Path.Combine(this.BaseDirectory, "test2");
|
||||
|
||||
if (!Directory.Exists(this.TestDirectory))
|
||||
Directory.CreateDirectory(this.TestDirectory);
|
||||
|
||||
this.TestFile = Path.Combine(this.TestDirectory, "test.html");
|
||||
this.TestFile = Path.Combine(this.BaseDirectory, "test.html");
|
||||
|
||||
if (!File.Exists(this.TestFile))
|
||||
File.WriteAllText(this.TestFile, "hello world");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeMultipleShouldWorkForFiles()
|
||||
public void ServeMultiple_File_ShouldWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveMultiPagePath(Path.Combine(Environment.CurrentDirectory, "test"), "../../test.html", null, out string resolvedPath, out bool isDirecotry).Should().BeTrue();
|
||||
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "/test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||
|
||||
isDirecotry.Should().BeFalse();
|
||||
isDirectory.Should().BeFalse();
|
||||
|
||||
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeMultiple_Directory_ShouldWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "/test2", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||
|
||||
isDirectory.Should().BeTrue();
|
||||
|
||||
resolvedPath.Should().BeEquivalentTo(this.TestDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeMultiple_NavigatingUp_Should_NotWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "../test.html", null, out string resolvedPath, out bool isDirectory).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeMultiple_Correct_NavigatingUp_Should_Work()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "a/b/../../test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||
|
||||
isDirectory.Should().BeFalse();
|
||||
|
||||
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeMultiple_NavigatingUp_Multiple_Should_NotWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveMultiPagePath(this.BaseDirectory, "test/../../test.html", null, out string resolvedPath, out bool isDirectory).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeSingle_File_ShouldWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||
|
||||
isDirectory.Should().BeFalse();
|
||||
|
||||
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeSingle_Directory_ShouldWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/test2", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||
|
||||
isDirectory.Should().BeTrue();
|
||||
|
||||
resolvedPath.Should().BeEquivalentTo(this.TestDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServeSingle_File_Route_ShouldWork()
|
||||
{
|
||||
HttpListenerResponseExtensions.ResolveSinglePagePath(this.BaseDirectory, "/a/b/test.html", null, out string resolvedPath, out bool isDirectory).Should().BeTrue();
|
||||
|
||||
isDirectory.Should().BeFalse();
|
||||
|
||||
resolvedPath.Should().BeEquivalentTo(this.TestFile);
|
||||
}
|
||||
|
@ -505,8 +505,10 @@ namespace MontoyaTech.Rest.Net
|
||||
/// <param name="basePath">The base path where to serve files from</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 ServeMultiPage(this HttpListenerResponse response, string basePath, HttpListenerRequest request, string indexFile = "index.html")
|
||||
public static HttpListenerResponse ServeMultiPage(this HttpListenerResponse response, string basePath, 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))
|
||||
{
|
||||
@ -517,11 +519,18 @@ namespace MontoyaTech.Rest.Net
|
||||
else
|
||||
{
|
||||
if (request.HttpMethod.Equals("head", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compress && (compressExtensions == null || compressExtensions.Contains(Path.GetExtension(resolvedPath))))
|
||||
return response.WithStatus(HttpStatusCode.OK).WithCompressedFile(resolvedPath);
|
||||
else
|
||||
return response.WithStatus(HttpStatusCode.OK).WithFile(resolvedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return response.WithStatus(HttpStatusCode.NotFound);
|
||||
@ -534,12 +543,48 @@ namespace MontoyaTech.Rest.Net
|
||||
|
||||
isDirectory = false;
|
||||
|
||||
var relativePath = Path.GetRelativePath(basePath, requestPath);
|
||||
//If the requestPath is pointing to nothing change that to the index file.
|
||||
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
|
||||
requestPath = indexFile;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(relativePath) || relativePath == ".")
|
||||
relativePath = indexFile;
|
||||
//Break th erequest path into it's components so we can enfore staying in the base path.
|
||||
var components = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
var absolutePath = Path.Combine(basePath, relativePath);
|
||||
for (int i = 0; i < components.Count; i++)
|
||||
{
|
||||
if (components[i].Trim() == "..")
|
||||
{
|
||||
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 (components.Count == 0)
|
||||
return false;
|
||||
|
||||
var absolutePath = Path.Combine(basePath, components.Separate(Path.DirectorySeparatorChar));
|
||||
|
||||
if (File.Exists(absolutePath))
|
||||
{
|
||||
@ -568,31 +613,104 @@ namespace MontoyaTech.Rest.Net
|
||||
/// <param name="basePath">The base path where to serve files from</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")
|
||||
public static HttpListenerResponse ServeSinglePage(this HttpListenerResponse response, string basePath, HttpListenerRequest request, string indexFile = "index.html", bool compress = false, HashSet<string> compressExtensions = null)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(basePath, request.Url.LocalPath);
|
||||
if (ResolveSinglePagePath(basePath, request.Url.LocalPath, indexFile, out string resolvedPath, out bool isDirectory))
|
||||
{
|
||||
if (isDirectory)
|
||||
{
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (request.HttpMethod.Equals("head", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compress && (compressExtensions == null || compressExtensions.Contains(Path.GetExtension(resolvedPath))))
|
||||
return response.WithStatus(HttpStatusCode.OK).WithCompressedFile(resolvedPath);
|
||||
else
|
||||
return response.WithStatus(HttpStatusCode.OK).WithFile(resolvedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return response.WithStatus(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(relativePath) || relativePath == ".")
|
||||
relativePath = indexFile;
|
||||
internal static bool ResolveSinglePagePath(string basePath, string requestPath, string indexFile, out string resolvedPath, out bool isDirectory)
|
||||
{
|
||||
resolvedPath = null;
|
||||
|
||||
var absolutePath = Path.Combine(basePath, relativePath);
|
||||
isDirectory = false;
|
||||
|
||||
//If the requestPath is pointing to nothing change that to the index file.
|
||||
if (string.IsNullOrWhiteSpace(requestPath) || requestPath == "/" || requestPath == ".")
|
||||
requestPath = indexFile;
|
||||
|
||||
//Break th erequest path into it's components so we can enfore staying in the base path.
|
||||
var components = requestPath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
for (int i = 0; i < components.Count; i++)
|
||||
{
|
||||
if (components[i].Trim() == "..")
|
||||
{
|
||||
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 (components.Count == 0)
|
||||
return false;
|
||||
|
||||
var absolutePath = Path.Combine(basePath, components.Separate(Path.DirectorySeparatorChar));
|
||||
|
||||
if (File.Exists(absolutePath))
|
||||
{
|
||||
if (request.HttpMethod.Equals("head", StringComparison.CurrentCultureIgnoreCase))
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
else
|
||||
return response.WithStatus(HttpStatusCode.OK).WithFile(absolutePath);
|
||||
resolvedPath = absolutePath;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Directory.Exists(absolutePath))
|
||||
{
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
resolvedPath = absolutePath;
|
||||
|
||||
isDirectory = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var components = relativePath.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
//Try to components that don't exist and try again
|
||||
while (components.Count > 0)
|
||||
{
|
||||
string path = Path.Combine(basePath, components[0]);
|
||||
@ -604,25 +722,26 @@ namespace MontoyaTech.Rest.Net
|
||||
}
|
||||
|
||||
if (components.Count == 0)
|
||||
return response.WithStatus(HttpStatusCode.NotFound);
|
||||
return false;
|
||||
|
||||
var combined = Path.Combine(basePath, components.Separate(Path.PathSeparator));
|
||||
absolutePath = Path.Combine(basePath, components.Separate(Path.PathSeparator));
|
||||
|
||||
if (File.Exists(combined))
|
||||
if (File.Exists(absolutePath))
|
||||
{
|
||||
if (request.HttpMethod.Equals("head", StringComparison.CurrentCultureIgnoreCase))
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
else
|
||||
return response.WithStatus(HttpStatusCode.OK).WithFile(absolutePath);
|
||||
resolvedPath = absolutePath;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Directory.Exists(combined))
|
||||
else if (Directory.Exists(absolutePath))
|
||||
{
|
||||
return response.WithNoBody().WithStatus(HttpStatusCode.NoContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
return response.WithStatus(HttpStatusCode.NotFound);
|
||||
resolvedPath = absolutePath;
|
||||
|
||||
isDirectory = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
||||
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<Version>1.6.2</Version>
|
||||
<Version>1.6.3</Version>
|
||||
<PackageReleaseNotes></PackageReleaseNotes>
|
||||
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
||||
</PropertyGroup>
|
||||
|
@ -22,7 +22,7 @@ namespace MontoyaTech.Rest.Net
|
||||
public static string Separate(this IList<string> input, char separator)
|
||||
{
|
||||
if (input == null || input.Count == 0)
|
||||
return null;
|
||||
return "";
|
||||
else if (input.Count < 2)
|
||||
return input[0];
|
||||
|
||||
@ -39,7 +39,7 @@ namespace MontoyaTech.Rest.Net
|
||||
public static string Separate(this IList<string> input, string separator)
|
||||
{
|
||||
if (input == null || input.Count == 0)
|
||||
return null;
|
||||
return "";
|
||||
else if (input.Count < 2)
|
||||
return input[0];
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user