Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codeql-js-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
permissions:
actions: read
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
build-and-test:
strategy:
matrix:
os: [windows-latest, ubuntu-20.04, macos-13]
os: [windows-latest, ubuntu-22.04, macos-13]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest Ubuntu image (version 24) doesn't include nuget - actions/runner-images#12086

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version 20 isn't supported any more - actions/runner-images#11101

Copy link
Copy Markdown
Contributor Author

@joscol joscol May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version 20 is still a required policy:

build-and-test (ubuntu-20.04)Expected — Waiting for status to be reported

I don't see how to change that. I probably don't have permission.

@michaelcfanning , do you know what I need to do?

runs-on: ${{matrix.os}}
steps:
- name: Check out code
Expand Down
1 change: 1 addition & 0 deletions ReleaseHistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* BUG: Fix error `ERR997.NoValidAnalysisTargets` when scanning symbolic link files.
* BUG: Fix `ERR999.UnhandledEngineException: System.IO.FileNotFoundException: Could not find file` when a file name or directory path contains URL-encoded characters.
* BUG: Fix error `ERR997.NoValidAnalysisTargets` when ambiguous file/directory references are provided to `OrderedFileSpecifier`. Previously, the code required an explicit directory separator to be added to the end of a directory path. Now, the code inspects the file system and assumes that a reference to an existing directory was intended by the user (even without a trailing separator).
* BUG: Fix `ArgumentException: Illegal characters in path` in `MultithreadedAnalyzeCommandBase`when analysis target URI contains characters that are illegal in the file system.
* NEW: Allow null archive uri in `MultithreadedZipArchiveArtifactProvider` (which indicates that enumerated artifact paths should not include the base archive).
* NEW: Update `LogTargetParseError(IAnalysisContext, Region, string, Exception)` to include optional exception argument to denote code location where parse error occurred.
* NEW: `MultithreadedAnalyzeCommandBase.EnumerateArtifact` now supports scanning into compressed (OPC) files. Initial support file extensions are: `.apk`, `.appx`, `.appxbundle`, `.docx`, `.epub`, `.jar`, `.msix`, `.msixbundle`, `.odp`, `.ods`, `.odt`, `.onepkg`, `.oxps`, `.pkg`, `.pptx`, `.unitypackage`, `.vsdx`, `.xps`, `.xlsx`, `.zip`.
Expand Down
2 changes: 1 addition & 1 deletion src/Sarif.Driver/Sdk/ExportConfigurationCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public override int Run(ExportConfigurationOptions exportOptions)
}
}

string extension = Path.GetExtension(exportOptions.OutputFilePath);
string extension = FileSystem.PathGetExtension(exportOptions.OutputFilePath);

if (extension.Equals(".xml", StringComparison.OrdinalIgnoreCase))
{
Expand Down
2 changes: 1 addition & 1 deletion src/Sarif.Driver/Sdk/ExportRulesMetadataCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public override int Run(ExportRulesMetadataOptions exportOptions)

string format = "";
string outputFilePath = exportOptions.OutputFilePath;
string extension = Path.GetExtension(outputFilePath);
string extension = FileSystem.PathGetExtension(outputFilePath);

switch (extension)
{
Expand Down
9 changes: 5 additions & 4 deletions src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public virtual int Run(TOptions options, ref TContext globalContext)
else
{
string etlFilePath =
Path.GetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase)
FileSystem.PathGetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase)
? $"{Path.GetFileNameWithoutExtension(globalContext.EventsFilePath)}.etl"
: globalContext.EventsFilePath;

Expand Down Expand Up @@ -244,7 +244,7 @@ private int Run(TContext globalContext)
globalContext.Logger.AnalysisStopped(globalContext.RuntimeErrors);
disposableLogger?.Dispose();

if (Path.GetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase))
if (FileSystem.PathGetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase))
{
var dumpEventsCommand = new DumpEventsCommand();

Expand Down Expand Up @@ -679,7 +679,8 @@ private async Task<bool> EnumerateArtifact(IEnumeratedArtifact artifact, TContex
return false;
}

string extension = Path.GetExtension(filePath);
string extension = FileSystem.PathGetExtension(filePath);

if (artifact.Uri.IsAbsoluteUri &&
string.IsNullOrEmpty(artifact.Uri.Query) &&
globalContext.OpcFileExtensions.Contains(extension))
Expand Down Expand Up @@ -940,7 +941,7 @@ protected virtual TContext InitializeConfiguration(string configurationFileName,

if (string.IsNullOrEmpty(configurationFileName)) { return context; }

string extension = Path.GetExtension(configurationFileName);
string extension = FileSystem.PathGetExtension(configurationFileName);

var configuration = new PropertiesDictionary();
if (extension.Equals(".xml", StringComparison.OrdinalIgnoreCase))
Expand Down
2 changes: 1 addition & 1 deletion src/Sarif.Multitool.Library/ApplyPolicyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public int Run(ApplyPolicyOptions options)

actualLog.ApplyPolicies();

string fileName = CommandUtilities.GetTransformedOutputFileName(options);
string fileName = CommandUtilities.GetTransformedOutputFileName(FileSystem, options);

WriteSarifFile(_fileSystem, actualLog, fileName, options.Minify);

Expand Down
4 changes: 2 additions & 2 deletions src/Sarif.Multitool.Library/CommandUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Multitool
{
internal static class CommandUtilities
{
internal static string GetTransformedOutputFileName(SingleFileOptionsBase options)
internal static string GetTransformedOutputFileName(IFileSystem fileSystem, SingleFileOptionsBase options)
{
string filePath = Path.GetFullPath(options.InputFilePath);

Expand All @@ -25,7 +25,7 @@ internal static string GetTransformedOutputFileName(SingleFileOptionsBase option
}

const string TransformedExtension = ".transformed.sarif";
string extension = Path.GetExtension(filePath);
string extension = fileSystem.PathGetExtension(filePath);

// For an input file named MyFile.sarif, returns MyFile.transformed.sarif.
if (extension.Equals(SarifConstants.SarifFileExtension, StringComparison.OrdinalIgnoreCase))
Expand Down
2 changes: 1 addition & 1 deletion src/Sarif.Multitool.Library/RewriteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public int Run(RewriteOptions options)
return FAILURE;
}

string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options);
string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(FileSystem, options);

SarifLog actualLog = null;

Expand Down
5 changes: 2 additions & 3 deletions src/Sarif.Multitool.Library/SarifValidationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.IO;

using Newtonsoft.Json.Linq;

Expand All @@ -30,8 +29,8 @@ public override bool IsValidAnalysisTarget
{
get
{
return Path.GetExtension(CurrentTarget.Uri.GetFileName()).Equals(SarifConstants.SarifFileExtension, StringComparison.OrdinalIgnoreCase) ||
Path.GetExtension(CurrentTarget.Uri.GetFileName()).Equals(".json", StringComparison.OrdinalIgnoreCase);
return FileSystem.PathGetExtension(CurrentTarget.Uri.GetFileName()).Equals(SarifConstants.SarifFileExtension, StringComparison.OrdinalIgnoreCase) ||
FileSystem.PathGetExtension(CurrentTarget.Uri.GetFileName()).Equals(".json", StringComparison.OrdinalIgnoreCase);
}
set
{
Expand Down
2 changes: 1 addition & 1 deletion src/Sarif.Multitool.Library/SuppressCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public int Run(SuppressOptions options)
options.ExpiryInDays,
options.Status).VisitSarifLog(currentSarifLog);

string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options);
string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(FileSystem, options);
if (options.SarifOutputVersion == SarifVersion.OneZeroZero)
{
var visitor = new SarifCurrentToVersionOneVisitor();
Expand Down
14 changes: 14 additions & 0 deletions src/Sarif/FileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -488,5 +488,19 @@ public string PathGetFileNameWithoutExtension(string path)
{
return Path.GetFileNameWithoutExtension(path);
}

/// <summary>
/// Returns the extension of the given path. The returned value includes the period (".") character of the extension
/// except when you have a terminal period when you get String.Empty, such as ".exe" or ".cpp".
///
/// While the latest version of Path.GetExtension will not throw if the path contains any illegal characters, older
/// versions of .NET, some of which are still in use, will. This method acts like the newer version and will not throw.
/// </summary>
/// <param name="path">The path to extract the extension from</param>
/// <returns>The file extension or null if the given path is null or if the given path does not include an extension.</returns>
public string PathGetExtension(string path)
{
return SarifUtilities.PathGetExtension(path);
}
}
}
11 changes: 11 additions & 0 deletions src/Sarif/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,5 +385,16 @@ public interface IFileSystem
/// The string returned by <see cref = "Path.GetFileName(string)"/>, minus the last period (.) and all characters following it.
/// </returns>
string PathGetFileNameWithoutExtension(string path);

/// <summary>
/// Returns the extension of the given path. The returned value includes the period (".") character of the extension
/// except when you have a terminal period when you get String.Empty, such as ".exe" or ".cpp".
///
/// While the latest version of Path.GetExtension will not throw if the path contains any illegal characters, older
/// versions of .NET, some of which are still in use, will. This method acts like the newer version and will not throw.
/// </summary>
/// <param name="path">The path to extract the extension from</param>
/// <returns>The file extension or null if the given path is null or if the given path does not include an extension.</returns>
string PathGetExtension(string path);
}
}
47 changes: 47 additions & 0 deletions src/Sarif/SarifUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -210,5 +211,51 @@ internal static Encoding GetEncodingFromName(string encodingName)
return null;
}
}

/// <summary>
/// Returns the extension of the given path. The returned value includes the period (".") character of the extension
/// except when you have a terminal period when you get String.Empty, such as ".exe" or ".cpp".
///
/// While the latest version of Path.GetExtension will not throw if the path contains any illegal characters, older
/// versions of .NET, some of which are still in use, will. This method acts like the newer version and will not throw.
/// </summary>
/// <param name="path">The path to extract the extension from</param>
/// <returns>The file extension or null if the given path is null or if the given path does not include an extension.</returns>
internal static string PathGetExtension(string path)
{
if (path == null)
{
return null;
}

// This function was copied from https://referencesource.microsoft.com/#mscorlib/system/io/path.cs,f424e433705aeb09
// with one change. The following line was commented out so that this method will not throw on
// illegal characters
// CheckInvalidPathChars(path);

int length = path.Length;
for (int i = length; --i >= 0;)
{
char ch = path[i];
if (ch == '.')
{
if (i != length - 1)
{
return path.Substring(i, length - i);
}
else
{
return string.Empty;
}
}

if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar)
{
break;
}
}

return string.Empty;
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

NIT: you introduced a blank line here

}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ protected override string ConstructTestOutputFromInputResource(string inputResou
RuleKindOption = AllRuleKinds// new List<RuleKind>() { RuleKind.Sarif },
};

var mockFileSystem = new Mock<IFileSystem>();
Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();

mockFileSystem.Setup(x => x.DirectoryExists(inputLogDirectory)).Returns(true);
mockFileSystem.Setup(x => x.DirectoryGetDirectories(It.IsAny<string>())).Returns(Array.Empty<string>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ protected override string ConstructTestOutputFromInputResource(string inputResou
RuleKindOption = new List<RuleKind>() { RuleKind.Gh, RuleKind.Sarif },
};

var mockFileSystem = new Mock<IFileSystem>();
Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();

mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny<string>())).Returns(1);
mockFileSystem.Setup(x => x.DirectoryExists(inputLogDirectory)).Returns(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -873,23 +873,13 @@ public void AnalyzeCommand_EncodedPaths()
var uri = new Uri(path, UriKind.Absolute);

string content = "foo foo";
var mockFileSystem = new Mock<IFileSystem>();
mockFileSystem.Setup(x => x.IsSymbolicLink(path)).Returns(false);
mockFileSystem.Setup(x => x.FileStreamLength(path)).Returns(content.Length);
mockFileSystem.Setup(x => x.FileInfoLength(path)).Returns(content.Length);
mockFileSystem.Setup(x => x.FileReadAllText(path)).Returns(content);
mockFileSystem.Setup(x => x.FileOpenRead(path)).Returns(new MemoryStream(Encoding.UTF8.GetBytes(content)));
IFileSystem mockFileSystem = MockFactory.MakeMockFileSystem(path, content);

var target = new EnumeratedArtifact(mockFileSystem.Object)
var target = new EnumeratedArtifact(mockFileSystem)
{
Uri = uri
};

var options = new TestAnalyzeOptions
{
PluginFilePaths = new[] { typeof(TestRule).Assembly.FullName },
};

var properties = new PropertiesDictionary();
properties.SetProperty(TestRule.Behaviors, TestRuleBehaviors.LogError);

Expand All @@ -910,6 +900,31 @@ public void AnalyzeCommand_EncodedPaths()
sarifLog.Runs[0].Results.Count.Should().Be(1);
}

[Fact]
public void AnalyzeCommand_IllegalPathCharInURL()
{
var sarifOutput = new StringBuilder();
var command = new TestMultithreadedAnalyzeCommand();
using var writer = new StringWriter(sarifOutput);
var logger = new SarifLogger(writer,
run: new Run { Tool = command.Tool },
levels: BaseLogger.ErrorWarningNote,
kinds: BaseLogger.Fail);

var target = new EnumeratedArtifact(FileSystem.Instance) { Uri = new Uri("http://example.com/some<character>test/bad\"characters\"path.txt"), Contents = "fake content" };

var context = new TestAnalysisContext
{
TargetsProvider = new ArtifactProvider(new[] { target }),
FailureLevels = BaseLogger.ErrorWarningNote,
ResultKinds = BaseLogger.Fail,
Logger = logger,
};

int result = command.Run(options: null, ref context);
result.Should().Be(0);
}

[Fact]
[Trait(TestTraits.WindowsOnly, "true")]
public void AnalyzeCommand_TracesInMemory()
Expand Down Expand Up @@ -1096,7 +1111,7 @@ public void MultithreadedMultithreadedAnalyzeCommandBase_EndToEndMultithreadedAn
mockStream.Setup(m => m.CanSeek).Returns(true);
mockStream.Setup(m => m.ReadByte()).Returns('a');

var mockFileSystem = new Mock<IFileSystem>();
Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();
mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny<string>())).Returns(2048);
mockFileSystem.Setup(x => x.DirectoryExists(It.IsAny<string>())).Returns(true);
mockFileSystem.Setup(x => x.DirectoryGetFiles(It.IsAny<string>(), specifier)).Returns(files);
Expand Down Expand Up @@ -1264,7 +1279,7 @@ public void MultithreadedMultithreadedAnalyzeCommandBase_TargetFileSizeTestCases
mockStream.Setup(m => m.CanSeek).Returns(true);
mockStream.Setup(m => m.ReadByte()).Returns('a');

var mockFileSystem = new Mock<IFileSystem>();
Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();

if (testCase.actualFileContent != null)
{
Expand Down Expand Up @@ -1347,7 +1362,7 @@ public void MultithreadedMultithreadedAnalyzeCommandBase_ErrorWhenHashing()
mockStream.Setup(m => m.ReadByte()).Returns('a');
mockStream.Setup(m => m.Seek(It.IsAny<long>(), It.IsAny<SeekOrigin>())).Throws(new IOException());

var mockFileSystem = new Mock<IFileSystem>();
Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();
mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny<string>())).Returns(2048);
mockFileSystem.Setup(x => x.DirectoryExists(It.IsAny<string>())).Returns(true);
mockFileSystem.Setup(x => x.DirectoryGetFiles(It.IsAny<string>(), specifier)).Returns(files);
Expand Down Expand Up @@ -1491,8 +1506,7 @@ public void MultithreadedAnalyzeCommandBase_LoadConfigurationFile(string configV
OutputFilePath = "",
};

var mockFileSystem = new Mock<IFileSystem>();

Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();
mockFileSystem.Setup(x => x.FileExists(It.IsAny<string>())).Returns(defaultFileExists);

var command = new TestMultithreadedAnalyzeCommand(mockFileSystem.Object);
Expand Down Expand Up @@ -2157,8 +2171,7 @@ private static IFileSystem CreateDefaultFileSystemForResultsCaching(IList<string

string logFileContents = Guid.NewGuid().ToString();

var mockFileSystem = new Mock<IFileSystem>();

Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();
mockFileSystem.Setup(x => x.DirectoryExists(It.IsAny<string>())).Returns(true);
mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny<string>())).Returns(2048);
mockFileSystem.Setup(x => x.DirectoryEnumerateFiles(It.IsAny<string>())).Returns(new string[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void ValidateCommand_AcceptsTargetFileWithSpaceInName()

string logFilePath = Path.Combine(LogFileDirectoryWithSpace, LogFileName);

var mockFileSystem = new Mock<IFileSystem>();
Mock<IFileSystem> mockFileSystem = MockFactory.MakeMockFileSystem();
mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny<string>())).Returns(1024);
mockFileSystem.Setup(x => x.DirectoryExists(LogFileDirectoryWithSpace)).Returns(true);
mockFileSystem.Setup(x => x.DirectoryEnumerateFiles(LogFileDirectoryWithSpace, It.IsAny<string>(), SearchOption.TopDirectoryOnly)).Returns(new[] { LogFileName });
Expand Down
Loading
Loading