Improved relative path checking based on file existence (#411)

* Improved relative path checking based on file existence

If the file exists at the relative path, then use it. If not, then use the path as is.

* Apply File.Exists logic to ReadResponseBodyAsString as well

* Make path handling more robust since path is user defined

* Unit tests for relative path feature

* Replace all back and forward slashes with system dependent DirectorySeparatorChar

* Attempt fix broken directory separator chars for Unix platforms

* Revert wrapping GetMappingFolder with CleanPath

* Move CleanPath logic to its own class

* Remove whitespace

* Remove more whitespace

* Improve CleanPath method

* Move PathUtils tests to separate class

Add another test to ResponseWithBodyFromFileTests

* Fix Response_ProvideResponse_WithBodyFromFile_InAdminMappingFolder

* Debug Linux CI build

* Debug Linux CI

* print all files from admin mappings folder

* Debug CleanPath

* Fix removed leading directory separator char in Linux breaks file path

Remove debugging statements

* Move combine to PathUtils

* PathUtils + PathUtilsTests

* Remove replicated (3x) tests throughout ResponseWithBodyFromFileTests

Co-authored-by: Stef Heyenrath <Stef.Heyenrath@gmail.com>
This commit is contained in:
Noah Lerner
2020-02-02 14:49:34 +02:00
committed by GitHub
parent 06ae9d72c3
commit 5e76a82a21
6 changed files with 155 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Handlers
@@ -80,20 +81,20 @@ namespace WireMock.Handlers
public byte[] ReadResponseBodyAsFile(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
// In case the path is a filename, the path will be adjusted to the MappingFolder.
path = PathUtils.CleanPath(path);
// If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is.
return File.ReadAllBytes(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
return File.ReadAllBytes(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public string ReadResponseBodyAsString(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
// In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is.
return File.ReadAllText(Path.GetFileName(path) == path ? Path.Combine(GetMappingFolder(), path) : path);
return File.ReadAllText(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>

View File

@@ -0,0 +1,36 @@
using System.IO;
namespace WireMock.Util
{
internal static class PathUtils
{
/// <summary>
/// Robust handling of the user defined path.
/// Also supports Unix and Windows platforms
/// </summary>
/// <param name="path">The path to clean</param>
public static string CleanPath(string path)
{
return path?.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
}
/// <summary>
/// Removes leading directory separator chars from the filepath, which could break Path.Combine
/// </summary>
/// <param name="path">The path to remove the loading DirectorySeparatorChars</param>
public static string RemoveLeadingDirectorySeparators(string path)
{
return path?.TrimStart(new[] { Path.DirectorySeparatorChar });
}
/// <summary>
/// Combine two paths
/// </summary>
/// <param name="root">The root path</param>
/// <param name="path">The path</param>
public static string Combine(string root, string path)
{
return Path.Combine(root, RemoveLeadingDirectorySeparators(path));
}
}
}

View File

@@ -1,4 +1,6 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
@@ -13,11 +15,10 @@ namespace WireMock.Net.Tests.ResponseBuilders
public class ResponseWithBodyFromFileTests
{
[Fact]
public async Task Response_ProvideResponse_WithBodyFromFile()
public async Task Response_ProvideResponse_WithBodyFromFile_AbsolutePath()
{
// Arrange
var server = WireMockServer.Start();
string path = Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings", "MyXmlResponse.xml");
server
@@ -36,14 +37,68 @@ namespace WireMock.Net.Tests.ResponseBuilders
);
// Act
var response1 = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/v1/content");
var response2 = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/v1/content");
var response3 = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/v1/content");
var response = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/v1/content");
// Assert
response1.Should().Contain("<hello>world</hello>");
response2.Should().Contain("<hello>world</hello>");
response3.Should().Contain("<hello>world</hello>");
response.Should().Contain("<hello>world</hello>");
}
[Fact]
public async Task Response_ProvideResponse_WithBodyFromFile_InSubDirectory()
{
// Arrange
var server = WireMockServer.Start();
string path = @"subdirectory/MyXmlResponse.xml";
server
.Given(
Request
.Create()
.UsingGet()
.WithPath("/v1/content")
)
.RespondWith(
Response
.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithHeader("Content-Type", "application/xml")
.WithBodyFromFile(path)
);
// Act
var response = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/v1/content");
// Assert
response.Should().Contain("<hello>world</hello>");
}
[Fact]
public async Task Response_ProvideResponse_WithBodyFromFile_InAdminMappingFolder()
{
// Arrange
var server = WireMockServer.Start();
string path = @"MyXmlResponse.xml";
server
.Given(
Request
.Create()
.UsingGet()
.WithPath("/v1/content")
)
.RespondWith(
Response
.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithHeader("Content-Type", "application/xml")
.WithBodyFromFile(path)
);
// Act
var response = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/v1/content");
// Assert
response.Should().Contain("<hello>world</hello>");
}
}
}

View File

@@ -0,0 +1,44 @@
using NFluent;
using System.IO;
using WireMock.Util;
using Xunit;
namespace WireMock.Net.Tests.Util
{
public class PathUtilsTests
{
[Theory]
[InlineData(@"subdirectory/MyXmlResponse.xml")]
[InlineData(@"subdirectory\MyXmlResponse.xml")]
public void PathUtils_CleanPath(string path)
{
// Act
var cleanPath = PathUtils.CleanPath(path);
// Assert
Check.That(cleanPath).Equals("subdirectory" + Path.DirectorySeparatorChar + "MyXmlResponse.xml");
}
[Theory]
[InlineData(null, null)]
[InlineData("", "")]
[InlineData("a", "a")]
[InlineData(@"/", "")]
[InlineData(@"//", "")]
[InlineData(@"//a", "a")]
[InlineData(@"\", "")]
[InlineData(@"\\", "")]
[InlineData(@"\\a", "a")]
public void PathUtils_CleanPath_RemoveLeadingDirectorySeparators(string path, string expected)
{
// Arrange
var cleanPath = PathUtils.CleanPath(path);
// Act
var withoutDirectorySeparators = PathUtils.RemoveLeadingDirectorySeparators(cleanPath);
// Assert
Check.That(withoutDirectorySeparators).Equals(expected);
}
}
}

View File

@@ -83,6 +83,9 @@
<None Update="__admin\mappings\MyXmlResponse.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\subdirectory\MyXmlResponse.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,3 @@
<xml>
<hello>world</hello>
</xml>