Skip to content

Commit

Permalink
Improved relative path checking based on file existence (#411)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
NoahLerner and StefH committed Feb 2, 2020
1 parent 06ae9d7 commit 5e76a82
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 13 deletions.
11 changes: 6 additions & 5 deletions src/WireMock.Net/Handlers/LocalFileSystemHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using WireMock.Util;
using WireMock.Validation;

namespace WireMock.Handlers
Expand Down Expand Up @@ -80,20 +81,20 @@ public void WriteMappingFile(string path, string text)
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"/>
Expand Down
36 changes: 36 additions & 0 deletions src/WireMock.Net/Util/PathUtils.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
Expand All @@ -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
Expand All @@ -36,14 +37,68 @@ public async Task Response_ProvideResponse_WithBodyFromFile()
);

// 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
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
response1.Should().Contain("<hello>world</hello>");
response2.Should().Contain("<hello>world</hello>");
response3.Should().Contain("<hello>world</hello>");
response.Should().Contain("<hello>world</hello>");
}
}
}
44 changes: 44 additions & 0 deletions test/WireMock.Net.Tests/Util/PathUtilsTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
3 changes: 3 additions & 0 deletions test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<xml>
<hello>world</hello>
</xml>

0 comments on commit 5e76a82

Please sign in to comment.