How to compare file paths from JsonConfigurationSources and Directory.GetFiles properly? - c#

I created an extension method to add all JSON configuration files to the IConfigurationBuilder
public static class IConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddJsonFilesFromDirectory(
this IConfigurationBuilder configurationBuilder,
IFileSystem fileSystem,
string pathToDirectory,
bool fileIsOptional,
bool reloadConfigurationOnFileChange,
string searchPattern = "*.json",
SearchOption directorySearchOption = SearchOption.AllDirectories)
{
var jsonFilePaths = fileSystem.Directory.EnumerateFiles(pathToDirectory, searchPattern, directorySearchOption);
foreach (var jsonFilePath in jsonFilePaths)
{
configurationBuilder.AddJsonFile(jsonFilePath, fileIsOptional, reloadConfigurationOnFileChange);
}
return configurationBuilder;
}
}
and want to create tests for it using xUnit. Based on
How do you mock out the file system in C# for unit testing?
I installed the packages System.IO.Abstractions and System.IO.Abstractions.TestingHelpers and started to test that JSON files from directories have been added
public sealed class IConfigurationBuilderExtensionsTests
{
private const string DirectoryRootPath = "./";
private readonly MockFileSystem _fileSystem;
public IConfigurationBuilderExtensionsTests()
{
_fileSystem = new MockFileSystem(new[]
{
"text.txt",
"config.json",
"dir/foo.json",
"dir/bar.xml",
"dir/sub/deeper/config.json"
}
.Select(filePath => Path.Combine(DirectoryRootPath, filePath))
.ToDictionary(
filePath => filePath,
_ => new MockFileData(string.Empty)));
}
[Theory]
[InlineData("*.json", SearchOption.AllDirectories)]
[InlineData("*.json", SearchOption.TopDirectoryOnly)]
// ... more theories go here ...
public void ItShouldAddJsonFilesFromDirectory(string searchPattern, SearchOption searchOption)
{
var addedJsonFilePaths = new ConfigurationBuilder()
.AddJsonFilesFromDirectory(_fileSystem, DirectoryRootPath, true, true, searchPattern, searchOption)
.Sources
.OfType<JsonConfigurationSource>()
.Select(jsonConfigurationSource => jsonConfigurationSource.Path)
.ToArray();
var jsonFilePathsFromTopDirectory = _fileSystem.Directory.GetFiles(DirectoryRootPath, searchPattern, searchOption);
Assert.True(addedJsonFilePaths.Length == jsonFilePathsFromTopDirectory.Length);
for (int i = 0; i < addedJsonFilePaths.Length; i++)
{
Assert.Equal(
jsonFilePathsFromTopDirectory[i],
Path.DirectorySeparatorChar + addedJsonFilePaths[i]);
}
}
}
The tests are passing but I would like to know if I could get in trouble when prepending Path.DirectorySeparatorChar to addedJsonFilePaths[i].
The problem is that
jsonFilePathsFromTopDirectory[i] returns "/config.json"
addedJsonFilePaths[i] returns "config.json"
so I have to prepend a slash at the beginning. Do you have any suggestions how to improve this / avoid later problems?

/config.json is an absolute path and config.json is a relative path, so to compare them you have to convert the relative path into absolute path by giving it a directory.
But this isn't the real problem, the document is not detailed enough (In fact it doesn't mention about this at all).
When you add a path by AddJsonFile extension method, it will automatically call FileConfigurationSource.ResolveFileProvider.
If no file provider has been set, for absolute Path, this will creates a physical file provider for the nearest existing directory.
This method convert the absolute path into a relative path, that's why /config.json becomes config.json, the directory info is put into an auto-generated file provider.
So to use the API correctly, you need change:
jsonConfigurationSource.Path
to:
jsonConfigurationSource.FileProvider.GetFileInfo(jsonConfigurationSource.Path).PhysicalPath
Or you can provide a FileProvider:
configurationBuilder.AddJsonFile(new NullFileProvider(), jsonFilePath, fileIsOptional, reloadConfigurationOnFileChange);

Instead of adding the directory separator character yourself, you might use System.IO.Path.Combine, which takes cares of that; it only adds one if needed.
Side note: since jsonFilePathsFromTopDirectory[i] returns /config.json having a / instead of a \, you might consider to use Path.AltDirectorySeparatorChar / instead of Path.DirectorySeparatorChar \.
In either way, Path.Combine deals with both.
Both below statements result in /config.json.
Path.Combine(Path.AltDirectorySeparatorChar.ToString(), "/config.json");
Path.Combine(Path.AltDirectorySeparatorChar.ToString(), "config.json");
Your assert statement would look like
Assert.Equal(
jsonFilePathsFromTopDirectory[i],
Path.Combine(Path.AltDirectorySeparatorChar.ToString(), addedJsonFilePaths[i])
);

The logic of comparing files seems alright, I don't find any outstanding problem with it, it is ok to prepend the "/" to match what you need.
Could be even better if you could use the System.IO.Path.DirectorySeparatorChar for the directory root path as well, so if you run on windows or Linux you will have no issues.
But there may be a conceptual problem with what you are doing.
To my understanding you aim to verify existence of specific configuration files required for your program to work right, if those files are missing than the program should fail. But that kind of failure due to missing configuration files, is an expected and valid result of your code.
Yet, you unit-test this as if missing files should fail the test, as if missing files are an indication that something wrong with your code, this is wrong.
Missing files are not indication of your code not working correct and Unit-test should not be used as a validator to make sure the files exist prior executing the program, you will likely agree that unit-test is not part of the actual process and it should only aim to test your code and not preconditions, the test should compare an expected result (mock result of your code) vs. actual result and certainly not meant to become part of the code. That unit test looks like a validator that should be in the code.
So unless those files are produced by your specific code (and not the deployment) there is no sense testing that. In such case you need to create a configuration validator code - and your unit test could test that instead. So it will test that the validator expected result with a mock input you provide. But the thing here is that you would know that you only testing the validation logic and not the actual existence of the files.

Related

ArgumentException : "the path is not of a legal form" when using the CallerFilePath attribute in a script

Context
I am trying to run a C# script (.csx file) programmatically from a program I will call ScriptRunner.exe here and that I wrote myself (because csi.exe doesn't output what I want).ScriptRunner.exe is a simple console application and its most interesting feature is to have the following line :
var state = await CSharpScript.RunAsync<int>(script, referencesAndUsings, globalArgs);
ScriptRunner.exe works great ! However...
Problem
The moment my script contains the following line :
static string GetCurrentFileName([System.Runtime.CompilerServices.CallerFilePath] string fileName = null) { return fileName; }
and in particular [System.Runtime.CompilerServices.CallerFilePath], I get an ArgumentException : "the path is not of a legal form" ; note that the latter doesn't appear if I use the same line from a C# Interactive through a #load command - which correctly shows the path of my .csx file.
Investigated elements until now
The stacktrace shows at System.IO.Path.LegacyNormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
I checked what seems to be the implementation
I checked by hand the path of my csx file ; there's no invalid path characters in the path to my csx, and no wildcards in it either.
I checked there was no reference issue with mscorlib
Maybe something is missing in the ScriptOptions (referencesAndUsings in my first sample code), I looked at it but... I don't seem to understand everything well enough
The way I created my ScriptOptions ("referencesAndUsings") looks like the following:
var myOptions = ScriptOptions.Default;
myOptions.AddReferences(new List<string>() { ... });
myOptions.AddImports(new List<string>() { ... });
This is the documentation for the CallerFilePath attribute
This is the documentaiont for the concept of Caller Information
What really saddens me is that it works in C# Interactive.
Question
Does anyone know why it wouldn't want to work when interpreted by my ScriptRunner.exe ; and how to make it work ?
In
var state = await CSharpScript.RunAsync<int>(script, referencesAndUsings, globalArgs);
script is the script itself (the contents of the csx file). As such it has no path. CSharpScript knows nothing about the path of your csx file. When you call GetCurrentFileName from within the script, there is no path information.
You need to specify a FilePath in ScriptOptions using WithFilePath(csxFilePath)

CWE 73 Error - Veracode Issue -.net application

I have been problem to solve an appointment of Veracode Scanner in my project. I created a function to validate a file but it did not pass in veracode scanner;
Here is the code of my function:
public static string GetSafeFileName(string fileNameToValidate)
{
fileNameToValidate= fileNameToValidate.Replace("'", "''").Replace(#"../", "").Replace(#"..\", "");
char[] blackListChars = System.IO.Path.GetInvalidPathChars();
char[] blackListFilename = System.IO.Path.GetInvalidFileNameChars();
foreach (var invalidChar in blackListChars)
{
if (fileNameToValidate.Contains(invalidChar))
{
fileNameToValidate = fileNameToValidate.Replace(invalidChar, ' ').Trim();
}
}
string fullPath = Path.GetFullPath(fileNameToValidate);
string directoryName = Path.GetDirectoryName(fullPath);
string fileName = Path.GetFileName(fullPath);
foreach (var invalidChar in blackListFilename)
{
if (fileName.Contains(invalidChar))
{
fileName = fileName.Replace(invalidChar, ' ').Trim();
}
}
string finalPath = Path.Combine(directoryName, fileName);
return finalPath;
}
What are the changes i have to fix the cwe 73 appointment in Veracode scanner? Anybody can help me?
My project is a windows forms running on .net 4.0
Thanks,
Bruno
Your problem is that Veracode doesn't actually detect what your code is doing, it detects what cleanser function is (or is not) being called. If you login to Veracode and search for help on "Supported Cleansing Functions" you'll find the list that are detected in your language.
Unfortunately, the list for .Net doesn't include anything for a CWE-73.
So, your solution is to specifically label your function as a cleanser for CWE-73 using a custom cleanser annotation. Search Veracode help for "Annotating Custom Cleansers".
using Veracode.Attributes;
[FilePathCleanser]
public static string GetSafeFileName(string fileNameToValidate)
{
...
That said, your implementation is not secure. Try passing in "C:\Windows\System32\notepad.exe" as a filename to be written to and you'll see the problem.
Blacklisting can only deal with what you expect. Whitelisting is a much stronger approach. Your approach should be based on a whitelist of directories, a whitelist of characters for filenames, and a whitelist of file extensions.
I have tried to solve similar problem but in java context. We used ESAPI as external library. You can review esapi project (for ideas how to realise a better solution in your project):https://github.com/ESAPI/esapi-java-legacy
Actually using esapi validator didn't solve the problem with veracode, but in my opinion reduce the risk for attack. With such a library you can enshure that user can't read file out of parent folder(you must hardcode such a directory) and that the user can't read a file with unproper extension -> you can add such a list with file extensions. But this library cant garantee that you can't manipulate files in the parent directory with allowed extensions.
So if you think that all needed verifications of filepaths are done you must ask for mitigation by design or develope a Map with all needed file resources in the project to enshure that there is no way the user to manipulate external files.
Also if you think that you have created a good filepath verification you can use cleanser annotation to mark your method. Here you can read more about custom cleansers
https://help.veracode.com/reader/DGHxSJy3Gn3gtuSIN2jkRQ/xrEjru~XmUHpO6~0FSae2Q

App Resources breaking in unit tests, due to uncontrollable shadow copying from ReSharper

Background:
On an application I am working on, I am writing tests using a mixture of Visual Studio 2015, SpecFlow, and ReSharper 2016.3 (I'll abbreviate this as R#, because I'm lazy.)
The application I am working on sends HTML-formatted emails, based on a template, which are stored in HTML files that are set as Copy Always in Visual Studio 2015.
Problem:
When I attempt to run my tests, I get the following exception:
System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Users\[Me]\AppData\Local\JetBrains\Installations\ReSharperPlatformVs14_001\Resources\SomeEmailTemplate.html`
The directory was not the output directory of the project I am working on, so I double-checked my R# settings, and confirmed that Shadow Copy was turned off. To be perfectly clear, my R# Shadow Copy checkbox is indeed unchecked.
The offending code is really pretty simple. Normal remedies like TestContext.CurrentContext.TestDirectory is not something I can, should, or even want to do, due to the fact that this code is needed by the application itself. It would be in appropriate to put test framework code in the application under test.
public class HtmlTemplateLog : ISectionedLog, IRenderableLog
{
#region Variables / Properties
private readonly string _rawHtml;
private readonly Dictionary<string, StringBuilder> _sectionDictionary = new Dictionary<string, StringBuilder>();
private StringBuilder _workingSection;
#endregion Variables / Properties
#region Constructor
public HtmlTemplateLog(string templateFile)
{
// This is specifically what breaks the tests.
_rawHtml = File.ReadAllText(templateFile)
.Replace("\r\n", string.Empty); // Replace all newlines with empty strings.
}
#endregion Constructor
#region Methods
// Methods work with the section dictionary.
// The RenderLog method does a string.Replace on all section names in the HTML.
// These methods aren't important for the question.
#endregion Methods
This is invoked as in the example below:
_someLog = new HtmlTemplateLog("Resources/SomeEmailTemplate.html");
// ...code...
_someLog.WriteLineInSection("{someSection}", "This is a message!");
string finalHtml = _someLog.RenderLog();
Questions:
1. I've turned off Shadow Copy on my R# tests. Why is this still doing Shadow Copies?
2. In what ways can I work around the fact that R# is not respecting the Shadow Copy checkbox, given that this is not test code, and thus that remedies that would normally be appropriate for test code aren't for this case?
I've discovered an answer for #2...though, it's rather clunky. I was inspired by the answer from #mcdon for a less-detailed version of the question.
Pretty much, if I don't want to resort to TestContext.CurrentContext.TestDirectory, then I need to make my local filenames into absolute paths. Unfortunately, R#'s broken Shadow Copy setting creates more work, since I can't just interrogate the currently-executing assembly - it will tell me the wrong thing. I need to get at the codebase instead and interrogate that.
I'm still a bit worried about what this code when we try to run it on the build server, however - I'm expecting 'unexpected' results. In that light, I'm wondering if the unexpected results can truly be called unexpected, given that I'm expecting that this won't work...
Anyways, the fix I came up with was this field-property system:
private string _presentWorkingDirectory;
private string PresentWorkingDirectory
{
get
{
if (!string.IsNullOrEmpty(_presentWorkingDirectory))
return _presentWorkingDirectory;
var assembly = Assembly.GetExecutingAssembly();
var codebase = new Uri(assembly.CodeBase);
var filePath = codebase.LocalPath;
var path = Directory.GetParent(filePath);
_presentWorkingDirectory = path.ToString();
return _presentWorkingDirectory;
}
}

How to get a ClickOnce application path and version number using C#/.NET code?

I want to get the path and version number of a ClickOnce application, provided the name of the ClickOnce application.
When I manually searched for it, I found it at the path as follows:
'C:\Users\krishnaim\AppData\Local\Apps\2.0\1HCG3KL0.K41\VO5BM4JR.RPO\head..tion_7446cb71d1187222_0005.0037_37dfcf0728461a82\HeadCount.exe'
But this keeps on changing, and it will become a hard-coded path. Is there another way to get a ClickOnce application (for example, HeadCount.exe which is already installed) path and version number using C#/.NET code?
It seems a little bizarre, but getting the current directory of the executing assembly is a bit tricky so my code below may be doing more than you think it should, but I assure you it is mitigating some issues where others may attempt to use Assembly.GetExecutingAssembly.Location property.
static public string AssemblyDirectory
{
get
{
//Don't use Assembly.GetExecutingAssembly().Location, instead use the CodeBase property
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return System.IO.Path.GetDirectoryName(path);
}
}
static public string AssemblyVersion
{
get
{
var asm = Assembly.GetExecutingAssembly();
//If you want the full four-part version number:
return asm.GetName().Version.ToString(4);
//You can reference asm.GetName().Version to get Major, Minor, MajorRevision, MinorRevision
//components individually and do with them as you please.
}
}
In order to do a ClickOnce application update you do not have to do so manually as long as you are using the standard deployment manifests (which I don't know how to ClickOnce unless you do use them).
The MSDN article Choosing a ClickOnce Update Strategy describes the different options for application updates.

How to get Directory while running unit test

Hi when running my unit test I'm wanting to get the directory my project is running in to retrieve a file.
Say I have a Test project named MyProject. Test I run:
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
and I receive "C:\\Source\\MyProject.Test\\bin\\Debug".
This is close to what I'm after. I don't want the bin\\Debug part.
Anyone know how instead I could get "C:\\Source\\MyProject.Test\\"?
I would do it differently.
I suggest making that file part of the solution/project. Then right-click -> Properties -> Copy To Output = Copy Always.
That file will then be copied to whatever your output directory is (e.g. C:\Source\MyProject.Test\bin\Debug).
Edit: Copy To Output = Copy if Newer is the better option
Usually you retrieve your solution directory (or project directory, depending on your solution structure) like this:
string solution_dir = Path.GetDirectoryName( Path.GetDirectoryName(
TestContext.CurrentContext.TestDirectory ) );
This will give you the parent directory of the "TestResults" folder created by testing projects.
Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
This will give you the directory you need....
as
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
gives nothing but
Directory.GetCurrentDirectory().
Have alook at this link
http://msdn.microsoft.com/en-us/library/system.appdomain.currentdomain.aspx
Further to #abhilash's comment.
This works in my EXE's, DLL's and when tested from a different UnitTest project in both Debug or Release modes:
var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location.Replace("bin\\Debug", string.Empty));
/// <summary>
/// Testing various directory sources in a Unit Test project
/// </summary>
/// <remarks>
/// I want to mimic the web app's App_Data folder in a Unit Test project:
/// A) Using Copy to Output Directory on each data file
/// D) Without having to set Copy to Output Directory on each data file
/// </remarks>
[TestMethod]
public void UT_PathsExist()
{
// Gets bin\Release or bin\Debug depending on mode
string baseA = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
Console.WriteLine(string.Format("Dir A:{0}", baseA));
Assert.IsTrue(System.IO.Directory.Exists(baseA));
// Gets bin\Release or bin\Debug depending on mode
string baseB = AppDomain.CurrentDomain.BaseDirectory;
Console.WriteLine(string.Format("Dir B:{0}", baseB));
Assert.IsTrue(System.IO.Directory.Exists(baseB));
// Returns empty string (or exception if you use .ToString()
string baseC = (string)AppDomain.CurrentDomain.GetData("DataDirectory");
Console.WriteLine(string.Format("Dir C:{0}", baseC));
Assert.IsFalse(System.IO.Directory.Exists(baseC));
// Move up two levels
string baseD = System.IO.Directory.GetParent(baseA).Parent.FullName;
Console.WriteLine(string.Format("Dir D:{0}", baseD));
Assert.IsTrue(System.IO.Directory.Exists(baseD));
// You need to set the Copy to Output Directory on each data file
var appPathA = System.IO.Path.Combine(baseA, "App_Data");
Console.WriteLine(string.Format("Dir A/App_Data:{0}", appPathA));
// C:/solution/UnitTestProject/bin/Debug/App_Data
Assert.IsTrue(System.IO.Directory.Exists(appPathA));
// You can work with data files in the project directory's App_Data folder (or any other test data folder)
var appPathD = System.IO.Path.Combine(baseD, "App_Data");
Console.WriteLine(string.Format("Dir D/App_Data:{0}", appPathD));
// C:/solution/UnitTestProject/App_Data
Assert.IsTrue(System.IO.Directory.Exists(appPathD));
}
I normally do it like that, and then I just add "..\..\" to the path to get up to the directory I want.
So what you could do is this:
var path = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + #"..\..\";
For NUnit this is what I do:
// Get the executing directory of the tests
string dir = NUnit.Framework.TestContext.CurrentContext.TestDirectory;
// Infer the project directory from there...2 levels up (depending on project type - for asp.net omit the latter Parent for a single level up)
dir = System.IO.Directory.GetParent(dir).Parent.FullName;
If required you can from there navigate back down to other directories if required:
dir = Path.Combine(dir, "MySubDir");
According to https://github.com/nunit/nunit/issues/742#issuecomment-121964506
For NUnit3 , System.Environment.CurrentDirector is never changed, so it shall be the path of solution.
Eg:
string szProjectPath = System.Environment.CurrentDirectory + #"\where\your\project\is";
I prefer fixed location rather than GetParent().
One drawback of GetParent is when build is changed from AnyCPU to x86, default path would be changed from bin\Debug to bin\x86\Debug.
Need to get another parent, and it's pain in the neck.
Also, you may still access to you test assemblies at TestContext.CurrentContext.TestDirectory and get output from TestContext.CurrentContext.WorkDirectory
Edit:
Note: There are many changes in NUnit3. I will suggest reading through the documentation about "Breaking changes"
The best solution I found was to put the file as an embedded resource on the test project and get it from my unit test. With this solution I don´t need to care about file paths.
I'm not sure if this helps, but this looks to be briefly touched on in the following question.
Visual Studio Solution Path environment variable
In general you may use this, regardless if running a test or console app or web app:
// returns the absolute path of assembly, file://C:/.../MyAssembly.dll
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
// returns the absolute path of assembly, i.e: C:\...\MyAssembly.dll
var location = Assembly.GetExecutingAssembly().Location;
If you are running NUnit, then:
// return the absolute path of directory, i.e. C:\...\
var testDirectory = TestContext.CurrentContext.TestDirectory;
My approach relies on getting the location of the unit testing assembly and then traversing upwards. In the following snippet the variable folderProjectLevel will give you the path to the Unit test project.
string pathAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
string folderAssembly = System.IO.Path.GetDirectoryName(pathAssembly);
if (folderAssembly.EndsWith("\\") == false) {
folderAssembly = folderAssembly + "\\";
}
string folderProjectLevel = System.IO.Path.GetFullPath(folderAssembly + "..\\..\\");
You can do it like this:
using System.IO;
Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, #"..\..\"));
use StackTrace
internal static class Extensions
{
public static string GetSourceDirectoryName(this Type type)
{
StackTrace stackTrace = new StackTrace(true);
foreach (var frame in stackTrace.GetFrames())
{
if (frame.GetMethod() is { } method && method.DeclaringType == type)
{
return Path.GetDirectoryName(frame.GetFileName());
}
}
throw new Exception($"未找到{type.Name}源文件目录");
}
}

Categories

Resources