Remove beta flag in version.json (#505)

* Remove beta flag in version.json

* Add the new ruleverifier options to the pack command.

* Typos

* Run formatting - no logic changes.

* Update Dependencies

* Refactor tests a bit

* Fix template issue after reformatting. Add missing integrity flags.
This commit is contained in:
Gabe Stocco 2022-08-30 15:09:42 -07:00 коммит произвёл GitHub
Родитель 7d93aa8d85
Коммит f65bd19c5c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
178 изменённых файлов: 18443 добавлений и 13065 удалений

Просмотреть файл

@ -1,78 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Microsoft.ApplicationInspector.CLI</RootNamespace>
<AssemblyName>ApplicationInspector.CLI</AssemblyName>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<Product>Application Inspector</Product>
<Company>Microsoft</Company>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does. This is a dotnet tool package. For the library, see Microsoft.CST.ApplicationInspector.</Description>
<FileVersion>0.0.0.0</FileVersion>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackAsTool>true</PackAsTool>
<PackageId>Microsoft.CST.ApplicationInspector.CLI</PackageId>
<PackageVersion>0.0.0</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<ToolCommandName>appinspector</ToolCommandName>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Microsoft.ApplicationInspector.CLI</RootNamespace>
<AssemblyName>ApplicationInspector.CLI</AssemblyName>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<Product>Application Inspector</Product>
<Company>Microsoft</Company>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does. This is a dotnet tool package. For the library, see Microsoft.CST.ApplicationInspector.</Description>
<FileVersion>0.0.0.0</FileVersion>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackAsTool>true</PackAsTool>
<PackageId>Microsoft.CST.ApplicationInspector.CLI</PackageId>
<PackageVersion>0.0.0</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<ToolCommandName>appinspector</ToolCommandName>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Writers\AnalyzeHtmlWriter.cs.foo" />
</ItemGroup>
<ItemGroup>
<None Remove="Writers\AnalyzeHtmlWriter.cs.foo"/>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="html\partials\_file_listing.liquid" />
<EmbeddedResource Include="html\partials\_report_overview.liquid" />
<EmbeddedResource Include="html\partials\_report_profile.liquid" />
<EmbeddedResource Include="html\partials\_report_summary.liquid" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="html\partials\_file_listing.liquid"/>
<EmbeddedResource Include="html\partials\_report_overview.liquid"/>
<EmbeddedResource Include="html\partials\_report_profile.liquid"/>
<EmbeddedResource Include="html\partials\_report_summary.liquid"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Logging\AppInspector.Logging.csproj" />
<ProjectReference Include="..\AppInspector.RulesEngine\AppInspector.RulesEngine.csproj" />
<ProjectReference Include="..\AppInspector\AppInspector.Commands.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Logging\AppInspector.Logging.csproj"/>
<ProjectReference Include="..\AppInspector.RulesEngine\AppInspector.RulesEngine.csproj"/>
<ProjectReference Include="..\AppInspector\AppInspector.Commands.csproj"/>
</ItemGroup>
<ItemGroup>
<None Update="html\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="html\resources\css\appinspector.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="html\resources\js\appinspector.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="preferences\tagreportgroups.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.txt" Pack="true" PackagePath="" />
<None Include="..\icon-128.png" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DotLiquid" Version="2.2.656" />
<PackageReference Include="Sarif.Sdk" Version="3.1.0" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
</ItemGroup>
<ItemGroup>
<None Update="html\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="html\resources\css\appinspector.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="html\resources\js\appinspector.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="preferences\tagreportgroups.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.txt" Pack="true" PackagePath=""/>
<None Include="..\icon-128.png" Pack="true" PackagePath=""/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DotLiquid" Version="2.2.656"/>
<PackageReference Include="Sarif.Sdk" Version="3.1.0"/>
<PackageReference Include="Serilog" Version="2.11.0"/>
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
<PackageReference Include="ShellProgressBar" Version="5.2.0"/>
</ItemGroup>
</Project>

Просмотреть файл

@ -1,170 +1,210 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
using System;
using System.Collections.Generic;
using CommandLine;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Logging;
using Microsoft.ApplicationInspector.RulesEngine;
namespace Microsoft.ApplicationInspector.CLI
namespace Microsoft.ApplicationInspector.CLI;
/// <summary>
/// CLI command option classes add output arguments to common properties for each command verb
/// </summary>
public record CLICommandOptions : LogOptions
{
using CommandLine;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.RulesEngine;
using System.Collections.Generic;
[Option('o', "output-file-path", Required = false, HelpText = "Output file path")]
public string? OutputFilePath { get; set; }
/// <summary>
/// CLI command option classes add output arguments to common properties for each command verb
/// </summary>
///
public record CLICommandOptions : LogOptions
{
[Option('o', "output-file-path", Required = false, HelpText = "Output file path")]
public string? OutputFilePath { get; set; }
[Option('f', "output-file-format", Required = false, HelpText = "Output format [json|text]", Default = "text")]
public string OutputFileFormat { get; set; } = "text";
}
public record CLICustomRulesCommandOptions: CLICommandOptions
{
[Option('r', "custom-rules-path", Required = false, HelpText = "Custom rules file or directory path")]
public string? CustomRulesPath { get; set; }
[Option("custom-languages-path", Required = false, HelpText = "Replace the default languages set with a custom languages.json.")]
public string? CustomLanguagesPath { get; set; }
[Option("custom-comments-path", Required = false, HelpText = "Replace the default comment specification set with a custom comments.json.")]
public string? CustomCommentsPath { get; set; }
[Option("disable-require-unique-ids", Required = false, HelpText = "Allow rules with duplicate IDs.")]
public bool DisableRequireUniqueIds { get; set; }
/// <summary>
/// Return a success error code when no matches were found but operation was apparently successful. Useful for CI scenarios
/// </summary>
[Option("success-error-code-with-no-matches", Required = false, HelpText = "When processing is apparently successful but there are no matches return a success error code - useful for CI.")]
public bool SuccessErrorCodeOnNoMatches { get; set; }
[Option("require-must-match", Required = false, HelpText = "When validating, require rules to have MustMatch self-tests.")]
public bool RequireMustMatch { get; set; }
[Option("require-must-not-match", Required = false, HelpText = "When validating, require rules to have MustNotMatch self-tests.")]
public bool RequireMustNotMatch { get; set; }
}
public record CLIAnalysisSharedCommandOptions : CLICustomRulesCommandOptions
{
[Option("disable-custom-rule-validation", Required = false, HelpText = "By default when providing custom rules they are validated. When set, validation will be skipped.")]
public bool DisableCustomRuleValidation { get; set; } = false;
[Option('i', "ignore-default-rules", Required = false, HelpText = "Exclude default rules bundled with application", Default = false)]
public bool IgnoreDefaultRules { get; set; }
[Option('F', "file-timeout", Required = false, HelpText = "Maximum amount of time in milliseconds to allow for processing each file. 0 is infinity. Default: 60000.", Default = 60000)]
public int FileTimeOut { get; set; } = 60000;
[Option('p', "processing-timeout", Required = false, HelpText = "Maximum amount of time in milliseconds to allow for processing. When NoShowProgress is set this includes enumeration time. 0 is infinity. Default: 0.", Default = 0)]
public int ProcessingTimeOut { get; set; }
[Option("enumeration-timeout", Required = false, HelpText = "Maximum amount of time in milliseconds to allow for enumerating. 0 is infinity. Default: 0.", Default = 0)]
public int EnumeratingTimeout { get; set; }
[Option("disable-archive-crawling", Required = false, HelpText = "Disable Archive Enumeration.")]
public bool DisableArchiveCrawling { get; set; }
[Option('S', "single-threaded", Required = false, HelpText = "Disables parallel processing. May be helpful for debugging with higher verbosity.")]
public bool SingleThread { get; set; }
[Option('g', "exclusion-globs", Required = false, HelpText = "Exclude source files that match glob patterns. Example: \"**/.git/**,*Tests*\". Use \"none\" to disable.", Default = new string[] { "**/bin/**", "**/obj/**", "**/.vs/**", "**/.git/**" }, Separator = ',')]
public IEnumerable<string> FilePathExclusions { get; set; } = System.Array.Empty<string>();
[Option('u', "scan-unknown-filetypes", Required = false, HelpText = "Scan files of unknown types.")]
public bool ScanUnknownTypes { get; set; }
[Option('c', "confidence-filters", Required = false, Separator = ',', HelpText = "Output only matches with specified confidence <value>,<value>. Default: Medium,High. [High|Medium|Low]", Default = new Confidence[]{ Confidence.High, Confidence.Medium })]
public IEnumerable<Confidence> ConfidenceFilters { get; set; } = new Confidence[] { Confidence.High, Confidence.Medium };
[Option("severity-filters", Required = false, Separator = ',',
HelpText =
"Output only matches with specified severity <value>,<value>. Default: All are enabled. [Critical|Important|Moderate|BestPractice|ManualReview]", Default = new Severity[] { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview })]
public IEnumerable<Severity> SeverityFilters { get; set; } = new Severity[] { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview };
}
/// <summary>
/// CLI command distinct arguments
/// </summary>
[Verb("analyze", HelpText = "Inspect source directory/file/compressed file (.tgz|zip) against defined characteristics")]
public record CLIAnalyzeCmdOptions : CLIAnalysisSharedCommandOptions
{
[Option('s', "source-path", Required = true, HelpText = "Source file or directory to inspect, comma separated", Separator = ',')]
public IEnumerable<string> SourcePath { get; set; } = System.Array.Empty<string>();
[Option('f', "output-file-format", Required = false, HelpText = "Output format [html|json|text]", Default = "html")]
public new string OutputFileFormat { get; set; } = "html";
[Option('e', "text-format", Required = false, HelpText = "Match text format specifiers", Default = "Tag:%T,Rule:%N,Ruleid:%R,Confidence:%X,File:%F,Sourcetype:%t,Line:%L,Sample:%m")]
public string TextOutputFormat { get; set; } = "Tag:%T,Rule:%N,Ruleid:%R,Confidence:%X,File:%F,Sourcetype:%t,Line:%L,Sample:%m";
[Option('N',"no-show-progress", Required = false, HelpText = "Disable progress information.")]
public bool NoShowProgressBar { get; set; }
[Option('C',"context-lines", Required = false, HelpText = "Number of lines of context on each side to include in excerpt (up to a maximum of 100 * NumLines characters on each side). 0 to skip exerpt. -1 to not extract samples or excerpts (implied by -t). When outputting sarif use -1 for no snippets, all other values ignored.")]
public int ContextLines { get; set; } = 3;
[Option('t',"tags-only", Required = false, HelpText = "Only get tags (no detailed match data). Ignored if output format is sarif.")]
public bool TagsOnly { get; set; }
[Option('n', "no-file-metadata", Required = false, HelpText = "Don't collect metadata about each individual file.")]
public bool NoFileMetadata { get; set; }
[Option('A', "allow-all-tags-in-build-files", Required = false, HelpText = "Allow all tags (not just Metadata tags) in files of type Build.")]
public bool AllowAllTagsInBuildFiles { get; set; }
[Option('M', "max-num-matches-per-tag", Required = false, HelpText = "If non-zero, and TagsOnly is not set, will ignore rules based on if all of their tags have been found the set value number of times.")]
public int MaxNumMatchesPerTag { get; set; } = 0;
[Option("base-path", Required = false, HelpText = "If set, when outputting sarif, will have paths made relative to the provided path.")]
public string? BasePath { get; set; } = null;
[Option("repository-uri", Required = false, HelpText = "If set, when outputting sarif, include this information.")]
public string? RepositoryUri { get; set; } = null;
[Option("commit-hash", Required = false, HelpText = "If set, when outputting sarif, include this information.")]
public string? CommitHash { get; set; } = null;
}
[Verb("tagdiff", HelpText = "Compares unique tag values between two source paths")]
public record CLITagDiffCmdOptions : CLIAnalysisSharedCommandOptions
{
[Option("src1", Required = true, HelpText = "Source 1 to compare (commaa separated)")]
public IEnumerable<string> SourcePath1 { get; set; } = System.Array.Empty<string>();
[Option("src2", Required = true, HelpText = "Source 2 to compare (commaa separated)")]
public IEnumerable<string> SourcePath2 { get; set; } = System.Array.Empty<string>();
[Option('t', "test-type", Required = false, HelpText = "Type of test to run [Equality|Inequality]", Default = TagTestType.Equality)]
public TagTestType TestType { get; set; } = TagTestType.Equality;
}
[Verb("exporttags", HelpText = "Export the list of tags associated with the specified rules. Does not scan source code.")]
public record CLIExportTagsCmdOptions : CLICommandOptions
{
[Option('r', "custom-rules-path", Required = false, HelpText = "Custom rules file or directory path")]
public string? CustomRulesPath { get; set; }
[Option('i', "ignore-default-rules", Required = false, HelpText = "Exclude default rules bundled with application", Default = false)]
public bool IgnoreDefaultRules { get; set; }
}
[Verb("verifyrules", HelpText = "Verify custom rules syntax is valid")]
public record CLIVerifyRulesCmdOptions : CLICustomRulesCommandOptions
{
[Option('d', "verify-default-rules", Required = false, Default = false, HelpText = "Verify the rules embedded in the binary.")]
public bool VerifyDefaultRules { get; set; }
}
[Verb("packrules", HelpText = "Combine multiple rule files into one file for ease in distribution")]
public record CLIPackRulesCmdOptions : CLICustomRulesCommandOptions
{
[Option('e', "pack-embedded-rules", Required = false, HelpText = "Pack the rules that are embedded in the application inspector binary.")]
public bool PackEmbeddedRules { get; set; }
}
[Option('f', "output-file-format", Required = false, HelpText = "Output format [json|text]", Default = "text")]
public string OutputFileFormat { get; set; } = "text";
}
public record CLICustomRulesCommandOptions : CLICommandOptions
{
[Option('r', "custom-rules-path", Required = false, HelpText = "Custom rules file or directory path")]
public string? CustomRulesPath { get; set; }
[Option("custom-languages-path", Required = false,
HelpText = "Replace the default languages set with a custom languages.json.")]
public string? CustomLanguagesPath { get; set; }
[Option("custom-comments-path", Required = false,
HelpText = "Replace the default comment specification set with a custom comments.json.")]
public string? CustomCommentsPath { get; set; }
[Option("disable-require-unique-ids", Required = false, HelpText = "Allow rules with duplicate IDs.")]
public bool DisableRequireUniqueIds { get; set; }
/// <summary>
/// Return a success error code when no matches were found but operation was apparently successful. Useful for CI
/// scenarios
/// </summary>
[Option("success-error-code-with-no-matches", Required = false,
HelpText =
"When processing is apparently successful but there are no matches return a success error code - useful for CI.")]
public bool SuccessErrorCodeOnNoMatches { get; set; }
[Option("require-must-match", Required = false,
HelpText = "When validating, require rules to have MustMatch self-tests.")]
public bool RequireMustMatch { get; set; }
[Option("require-must-not-match", Required = false,
HelpText = "When validating, require rules to have MustNotMatch self-tests.")]
public bool RequireMustNotMatch { get; set; }
}
public record CLIAnalysisSharedCommandOptions : CLICustomRulesCommandOptions
{
[Option("disable-custom-rule-validation", Required = false,
HelpText = "By default when providing custom rules they are validated. When set, validation will be skipped.")]
public bool DisableCustomRuleValidation { get; set; } = false;
[Option('i', "ignore-default-rules", Required = false, HelpText = "Exclude default rules bundled with application",
Default = false)]
public bool IgnoreDefaultRules { get; set; }
[Option('F', "file-timeout", Required = false,
HelpText =
"Maximum amount of time in milliseconds to allow for processing each file. 0 is infinity. Default: 60000.",
Default = 60000)]
public int FileTimeOut { get; set; } = 60000;
[Option('p', "processing-timeout", Required = false,
HelpText =
"Maximum amount of time in milliseconds to allow for processing. When NoShowProgress is set this includes enumeration time. 0 is infinity. Default: 0.",
Default = 0)]
public int ProcessingTimeOut { get; set; }
[Option("enumeration-timeout", Required = false,
HelpText = "Maximum amount of time in milliseconds to allow for enumerating. 0 is infinity. Default: 0.",
Default = 0)]
public int EnumeratingTimeout { get; set; }
[Option("disable-archive-crawling", Required = false, HelpText = "Disable Archive Enumeration.")]
public bool DisableArchiveCrawling { get; set; }
[Option('S', "single-threaded", Required = false,
HelpText = "Disables parallel processing. May be helpful for debugging with higher verbosity.")]
public bool SingleThread { get; set; }
[Option('g', "exclusion-globs", Required = false,
HelpText =
"Exclude source files that match glob patterns. Example: \"**/.git/**,*Tests*\". Use \"none\" to disable.",
Default = new[] { "**/bin/**", "**/obj/**", "**/.vs/**", "**/.git/**" }, Separator = ',')]
public IEnumerable<string> FilePathExclusions { get; set; } = Array.Empty<string>();
[Option('u', "scan-unknown-filetypes", Required = false, HelpText = "Scan files of unknown types.")]
public bool ScanUnknownTypes { get; set; }
[Option('c', "confidence-filters", Required = false, Separator = ',',
HelpText =
"Output only matches with specified confidence <value>,<value>. Default: Medium,High. [High|Medium|Low]",
Default = new[] { Confidence.High, Confidence.Medium })]
public IEnumerable<Confidence> ConfidenceFilters { get; set; } = new[] { Confidence.High, Confidence.Medium };
[Option("severity-filters", Required = false, Separator = ',',
HelpText =
"Output only matches with specified severity <value>,<value>. Default: All are enabled. [Critical|Important|Moderate|BestPractice|ManualReview]",
Default = new[]
{ Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview })]
public IEnumerable<Severity> SeverityFilters { get; set; } = new[]
{ Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview };
}
/// <summary>
/// CLI command distinct arguments
/// </summary>
[Verb("analyze", HelpText = "Inspect source directory/file/compressed file (.tgz|zip) against defined characteristics")]
public record CLIAnalyzeCmdOptions : CLIAnalysisSharedCommandOptions
{
[Option('s', "source-path", Required = true, HelpText = "Source file or directory to inspect, comma separated",
Separator = ',')]
public IEnumerable<string> SourcePath { get; set; } = Array.Empty<string>();
[Option('f', "output-file-format", Required = false, HelpText = "Output format [html|json|text]", Default = "html")]
public new string OutputFileFormat { get; set; } = "html";
[Option('e', "text-format", Required = false, HelpText = "Match text format specifiers",
Default = "Tag:%T,Rule:%N,Ruleid:%R,Confidence:%X,File:%F,Sourcetype:%t,Line:%L,Sample:%m")]
public string TextOutputFormat { get; set; } =
"Tag:%T,Rule:%N,Ruleid:%R,Confidence:%X,File:%F,Sourcetype:%t,Line:%L,Sample:%m";
[Option('N', "no-show-progress", Required = false, HelpText = "Disable progress information.")]
public bool NoShowProgressBar { get; set; }
[Option('C', "context-lines", Required = false,
HelpText =
"Number of lines of context on each side to include in excerpt (up to a maximum of 100 * NumLines characters on each side). 0 to skip exerpt. -1 to not extract samples or excerpts (implied by -t). When outputting sarif use -1 for no snippets, all other values ignored.")]
public int ContextLines { get; set; } = 3;
[Option('t', "tags-only", Required = false,
HelpText = "Only get tags (no detailed match data). Ignored if output format is sarif.")]
public bool TagsOnly { get; set; }
[Option('n', "no-file-metadata", Required = false, HelpText = "Don't collect metadata about each individual file.")]
public bool NoFileMetadata { get; set; }
[Option('A', "allow-all-tags-in-build-files", Required = false,
HelpText = "Allow all tags (not just Metadata tags) in files of type Build.")]
public bool AllowAllTagsInBuildFiles { get; set; }
[Option('M', "max-num-matches-per-tag", Required = false,
HelpText =
"If non-zero, and TagsOnly is not set, will ignore rules based on if all of their tags have been found the set value number of times.")]
public int MaxNumMatchesPerTag { get; set; } = 0;
[Option("base-path", Required = false,
HelpText = "If set, when outputting sarif, will have paths made relative to the provided path.")]
public string? BasePath { get; set; } = null;
[Option("repository-uri", Required = false, HelpText = "If set, when outputting sarif, include this information.")]
public string? RepositoryUri { get; set; } = null;
[Option("commit-hash", Required = false, HelpText = "If set, when outputting sarif, include this information.")]
public string? CommitHash { get; set; } = null;
}
[Verb("tagdiff", HelpText = "Compares unique tag values between two source paths")]
public record CLITagDiffCmdOptions : CLIAnalysisSharedCommandOptions
{
[Option("src1", Required = true, HelpText = "Source 1 to compare (commaa separated)")]
public IEnumerable<string> SourcePath1 { get; set; } = Array.Empty<string>();
[Option("src2", Required = true, HelpText = "Source 2 to compare (commaa separated)")]
public IEnumerable<string> SourcePath2 { get; set; } = Array.Empty<string>();
[Option('t', "test-type", Required = false, HelpText = "Type of test to run [Equality|Inequality]",
Default = TagTestType.Equality)]
public TagTestType TestType { get; set; } = TagTestType.Equality;
}
[Verb("exporttags",
HelpText = "Export the list of tags associated with the specified rules. Does not scan source code.")]
public record CLIExportTagsCmdOptions : CLICommandOptions
{
[Option('r', "custom-rules-path", Required = false, HelpText = "Custom rules file or directory path")]
public string? CustomRulesPath { get; set; }
[Option('i', "ignore-default-rules", Required = false, HelpText = "Exclude default rules bundled with application",
Default = false)]
public bool IgnoreDefaultRules { get; set; }
}
[Verb("verifyrules", HelpText = "Verify custom rules syntax is valid")]
public record CLIVerifyRulesCmdOptions : CLICustomRulesCommandOptions
{
[Option('d', "verify-default-rules", Required = false, Default = false,
HelpText = "Verify the rules embedded in the binary.")]
public bool VerifyDefaultRules { get; set; }
}
[Verb("packrules", HelpText = "Combine multiple rule files into one file for ease in distribution")]
public record CLIPackRulesCmdOptions : CLICustomRulesCommandOptions
{
[Option('e', "pack-embedded-rules", Required = false,
HelpText = "Pack the rules that are embedded in the application inspector binary.")]
public bool PackEmbeddedRules { get; set; }
}

Просмотреть файл

@ -1,434 +1,436 @@
//Copyright(c) Microsoft Corporation.All rights reserved.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using CommandLine.Text;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.Logging;
using Microsoft.Extensions.Logging;
using ShellProgressBar;
namespace Microsoft.ApplicationInspector.CLI
namespace Microsoft.ApplicationInspector.CLI;
public static class Program
{
using CommandLine;
using Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using ShellProgressBar;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ILogger = Extensions.Logging.ILogger;
private static ILoggerFactory loggerFactory = new LoggerFactory();
public static class Program
/// <summary>
/// CLI program entry point which defines command verbs and options to running
/// </summary>
/// <param name="args"></param>
public static int Main(string[] args)
{
private static ILoggerFactory loggerFactory = new LoggerFactory();
var finalResult = (int)Utils.ExitCode.CriticalError;
/// <summary>
/// CLI program entry point which defines command verbs and options to running
/// </summary>
/// <param name="args"></param>
public static int Main(string[] args)
Utils.CLIExecutionContext = true; //set manually at start from CLI
try
{
int finalResult = (int)Common.Utils.ExitCode.CriticalError;
Common.Utils.CLIExecutionContext = true;//set manually at start from CLI
try
{
var argsResult = new Parser(settings =>
{
settings.CaseInsensitiveEnumValues = true;
}).ParseArguments<CLIAnalyzeCmdOptions,
var argsResult = new Parser(settings => { settings.CaseInsensitiveEnumValues = true; })
.ParseArguments<CLIAnalyzeCmdOptions,
CLITagDiffCmdOptions,
CLIExportTagsCmdOptions,
CLIVerifyRulesCmdOptions,
CLIPackRulesCmdOptions>(args);
return argsResult.MapResult(
(CLIAnalyzeCmdOptions cliAnalyzeCmdOptions) => VerifyOutputArgsAndRunAnalyze(cliAnalyzeCmdOptions),
(CLITagDiffCmdOptions cliTagDiffCmdOptions) => VerifyOutputArgsAndRunTagDiff(cliTagDiffCmdOptions),
(CLIExportTagsCmdOptions cliExportTagsCmdOptions) => VerifyOutputArgsAndRunExportTags(cliExportTagsCmdOptions),
(CLIVerifyRulesCmdOptions cliVerifyRulesCmdOptions) => VerifyOutputArgsAndRunVerifyRules(cliVerifyRulesCmdOptions),
(CLIPackRulesCmdOptions cliPackRulesCmdOptions) => VerifyOutputArgsAndRunPackRules(cliPackRulesCmdOptions),
errs =>
{
Console.WriteLine(HelpText.AutoBuild(argsResult));
return (int)Common.Utils.ExitCode.CriticalError;
});
}
catch (Exception e)
{
var logger = loggerFactory.CreateLogger("Program");
logger.LogError("Uncaught exception: {type}:{message}. {stackTrace}", e.GetType().Name, e.Message, e.StackTrace);
loggerFactory.Dispose();
}
return finalResult;
}
private static int VerifyOutputArgsAndRunTagDiff(CLITagDiffCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
CommonOutputChecks(options);
return RunTagDiffCommand(options);
}
private static int VerifyOutputArgsAndRunExportTags(CLIExportTagsCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
CommonOutputChecks(options);
return RunExportTagsCommand(options);
}
private static int VerifyOutputArgsAndRunVerifyRules(CLIVerifyRulesCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
CommonOutputChecks(options);
return RunVerifyRulesCommand(options);
}
private static int VerifyOutputArgsAndRunPackRules(CLIPackRulesCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
ILogger logger = loggerFactory.CreateLogger("Program");
if (string.IsNullOrEmpty(options.OutputFilePath))
{
logger.LogError(MsgHelp.GetString(MsgHelp.ID.PACK_MISSING_OUTPUT_ARG));
throw new OpException(MsgHelp.GetString(MsgHelp.ID.PACK_MISSING_OUTPUT_ARG));
}
else
{
CommonOutputChecks(options);
}
return RunPackRulesCommand(options);
}
private static int VerifyOutputArgsAndRunAnalyze(CLIAnalyzeCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
//analyze with html format limit checks
if (options.OutputFileFormat == "html")
{
options.OutputFilePath ??= "output.html";
string extensionCheck = Path.GetExtension(options.OutputFilePath);
if (extensionCheck is not ".html" and not ".htm")
return argsResult.MapResult(
(CLIAnalyzeCmdOptions cliAnalyzeCmdOptions) => VerifyOutputArgsAndRunAnalyze(cliAnalyzeCmdOptions),
(CLITagDiffCmdOptions cliTagDiffCmdOptions) => VerifyOutputArgsAndRunTagDiff(cliTagDiffCmdOptions),
(CLIExportTagsCmdOptions cliExportTagsCmdOptions) =>
VerifyOutputArgsAndRunExportTags(cliExportTagsCmdOptions),
(CLIVerifyRulesCmdOptions cliVerifyRulesCmdOptions) =>
VerifyOutputArgsAndRunVerifyRules(cliVerifyRulesCmdOptions),
(CLIPackRulesCmdOptions cliPackRulesCmdOptions) =>
VerifyOutputArgsAndRunPackRules(cliPackRulesCmdOptions),
errs =>
{
loggerFactory.CreateLogger("Program").LogInformation(MsgHelp.GetString(MsgHelp.ID.ANALYZE_HTML_EXTENSION));
}
}
if (CommonOutputChecks(options))
{
return RunAnalyzeCommand(options);
}
else
{
return (int)Utils.ExitCode.CriticalError;
}
Console.WriteLine(HelpText.AutoBuild(argsResult));
return (int)Utils.ExitCode.CriticalError;
});
}
/// <summary>
/// Checks that either output filepath is valid or console verbosity is not visible to ensure
/// some output can be achieved...other command specific inputs that are relevant to both CLI
/// and NuGet callers are checked by the commands themselves
/// </summary>
/// <param name="options"></param>
private static bool CommonOutputChecks(CLICommandOptions options)
catch (Exception e)
{
//validate requested format
string fileFormatArg = options.OutputFileFormat;
string[] validFormats =
{
"html",
"text",
"json",
"sarif"
};
var logger = loggerFactory.CreateLogger("Program");
string[] checkFormats;
if (options is CLIAnalyzeCmdOptions cliAnalyzeOptions)
logger.LogError("Uncaught exception: {type}:{message}. {stackTrace}", e.GetType().Name, e.Message,
e.StackTrace);
loggerFactory.Dispose();
}
return finalResult;
}
private static int VerifyOutputArgsAndRunTagDiff(CLITagDiffCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
CommonOutputChecks(options);
return RunTagDiffCommand(options);
}
private static int VerifyOutputArgsAndRunExportTags(CLIExportTagsCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
CommonOutputChecks(options);
return RunExportTagsCommand(options);
}
private static int VerifyOutputArgsAndRunVerifyRules(CLIVerifyRulesCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
CommonOutputChecks(options);
return RunVerifyRulesCommand(options);
}
private static int VerifyOutputArgsAndRunPackRules(CLIPackRulesCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
var logger = loggerFactory.CreateLogger("Program");
if (string.IsNullOrEmpty(options.OutputFilePath))
{
logger.LogError(MsgHelp.GetString(MsgHelp.ID.PACK_MISSING_OUTPUT_ARG));
throw new OpException(MsgHelp.GetString(MsgHelp.ID.PACK_MISSING_OUTPUT_ARG));
}
CommonOutputChecks(options);
return RunPackRulesCommand(options);
}
private static int VerifyOutputArgsAndRunAnalyze(CLIAnalyzeCmdOptions options)
{
loggerFactory = options.GetLoggerFactory();
//analyze with html format limit checks
if (options.OutputFileFormat == "html")
{
options.OutputFilePath ??= "output.html";
var extensionCheck = Path.GetExtension(options.OutputFilePath);
if (extensionCheck is not ".html" and not ".htm")
loggerFactory.CreateLogger("Program")
.LogInformation(MsgHelp.GetString(MsgHelp.ID.ANALYZE_HTML_EXTENSION));
}
if (CommonOutputChecks(options))
return RunAnalyzeCommand(options);
return (int)Utils.ExitCode.CriticalError;
}
/// <summary>
/// Checks that either output filepath is valid or console verbosity is not visible to ensure
/// some output can be achieved...other command specific inputs that are relevant to both CLI
/// and NuGet callers are checked by the commands themselves
/// </summary>
/// <param name="options"></param>
private static bool CommonOutputChecks(CLICommandOptions options)
{
//validate requested format
var fileFormatArg = options.OutputFileFormat;
string[] validFormats =
{
"html",
"text",
"json",
"sarif"
};
var logger = loggerFactory.CreateLogger("Program");
string[] checkFormats;
if (options is CLIAnalyzeCmdOptions cliAnalyzeOptions)
{
checkFormats = validFormats;
fileFormatArg = cliAnalyzeOptions.OutputFileFormat;
}
else if (options is CLIPackRulesCmdOptions cliPackRulesOptions)
{
checkFormats = validFormats.Skip(2).Take(1).ToArray();
fileFormatArg = cliPackRulesOptions.OutputFileFormat;
}
else
{
checkFormats = validFormats.Skip(1).Take(2).ToArray();
}
var isValidFormat = checkFormats.Any(v => v.Equals(fileFormatArg.ToLower()));
if (!isValidFormat)
{
logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_ARG_VALUE), "-f");
return false;
}
if (!string.IsNullOrEmpty(options.OutputFilePath) && !CanWritePath(options.OutputFilePath))
{
logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_LOG_PATH), options.OutputFilePath);
return false;
}
return true;
}
/// <summary>
/// Ensure output file path can be written to
/// </summary>
/// <param name="filePath"></param>
private static bool CanWritePath(string filePath)
{
try
{
File.WriteAllText(filePath, ""); //verify ability to write to location
}
catch (Exception)
{
return false;
}
return true;
}
/// <summary>
/// This method gets a logging factory with Console disabled when the progress bar is enabled. Does not change the
/// original options.
/// </summary>
/// <param name="cliOptions">The original options.</param>
/// <returns>A logging factory with adjusted settings.</returns>
private static ILoggerFactory GetAdjustedFactory(CLIAnalyzeCmdOptions cliOptions)
{
return new LogOptions
{
ConsoleVerbosityLevel = cliOptions.ConsoleVerbosityLevel,
LogFileLevel = cliOptions.LogFileLevel,
LogFilePath = cliOptions.LogFilePath,
DisableConsoleOutput = !cliOptions.NoShowProgressBar || cliOptions.DisableConsoleOutput
}.GetLoggerFactory();
}
private static (bool initialSuccess, bool backupSuccess, string? backupLocation) TryWriteResults(
ResultsWriter writer, AnalyzeResult analyzeResult, CLIAnalyzeCmdOptions cliOptions, ILogger logger)
{
try
{
writer.Write(analyzeResult, cliOptions);
return (true, false, null);
}
catch (Exception e)
{
logger.LogError("Exception hit while writing. {Type} : {Message}", e.GetType().Name,
e.Message);
analyzeResult.ResultCode = AnalyzeResult.ExitCode.CriticalError;
// If HTML
if (cliOptions.OutputFileFormat.Equals("html", StringComparison.InvariantCultureIgnoreCase))
{
checkFormats = validFormats;
fileFormatArg = cliAnalyzeOptions.OutputFileFormat;
}
else if (options is CLIPackRulesCmdOptions cliPackRulesOptions)
{
checkFormats = validFormats.Skip(2).Take(1).ToArray();
fileFormatArg = cliPackRulesOptions.OutputFileFormat;
var alternatepath = "backupOutput.json";
var optionsCopy = cliOptions with { OutputFileFormat = "json", OutputFilePath = alternatepath };
try
{
writer.Write(analyzeResult, optionsCopy);
logger.LogInformation("Failed to write HTML report. Exported JSON instead at {AlternatePath}",
alternatepath);
return (false, true, alternatepath);
}
catch (Exception e2)
{
logger.LogError("Exception hit while writing backup of result object. {Type} : {Message}",
e2.GetType().Name,
e2.Message);
}
}
}
return (false, false, null);
}
private static int RunAnalyzeCommand(CLIAnalyzeCmdOptions cliOptions)
{
// If the user manually specified -1 this means they also don't even want the snippet in sarif, so we respect that option
// Otherwise if we are outputting sarif we don't use any of the context information so we set context to 0, if not sarif leave it alone.
var isSarif = cliOptions.OutputFileFormat.Equals("sarif", StringComparison.InvariantCultureIgnoreCase);
var numContextLines = cliOptions.ContextLines == -1 ? cliOptions.ContextLines :
isSarif ? 0 : cliOptions.ContextLines;
// tagsOnly isn't compatible with sarif output, we choose to prioritize the choice of sarif.
var tagsOnly = !isSarif && cliOptions.TagsOnly;
var logger = cliOptions.GetLoggerFactory().CreateLogger("Program");
if (!cliOptions.NoShowProgressBar)
{
if (string.IsNullOrEmpty(cliOptions.LogFilePath))
logger.LogInformation(
"Progress bar is enabled so console output will be suppressed. No LogFilePath has been configured so you will not receive log messages");
else
{
checkFormats = validFormats.Skip(1).Take(2).ToArray();
}
bool isValidFormat = checkFormats.Any(v => v.Equals(fileFormatArg.ToLower()));
if (!isValidFormat)
{
logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_ARG_VALUE), "-f");
return false;
}
if (!string.IsNullOrEmpty(options.OutputFilePath) && !CanWritePath(options.OutputFilePath))
{
logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_LOG_PATH), options.OutputFilePath);
return false;
}
return true;
logger.LogInformation(
"Progress bar is enabled so console output will be suppressed. For log messages check the log at {LogPath}",
cliOptions.LogFilePath);
}
/// <summary>
/// Ensure output file path can be written to
/// </summary>
/// <param name="filePath"></param>
private static bool CanWritePath(string filePath)
var adjustedFactory = GetAdjustedFactory(cliOptions);
AnalyzeCommand command = new(new AnalyzeOptions
{
try
{
File.WriteAllText(filePath, "");//verify ability to write to location
}
catch (Exception)
{
return false;
}
return true;
SourcePath = cliOptions.SourcePath ?? Array.Empty<string>(),
CustomRulesPath = cliOptions.CustomRulesPath ?? "",
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
IgnoreDefaultRules = cliOptions.IgnoreDefaultRules,
ConfidenceFilters = cliOptions.ConfidenceFilters,
SeverityFilters = cliOptions.SeverityFilters,
FilePathExclusions = cliOptions.FilePathExclusions,
SingleThread = cliOptions.SingleThread,
NoShowProgress = cliOptions.NoShowProgressBar,
FileTimeOut = cliOptions.FileTimeOut,
ProcessingTimeOut = cliOptions.ProcessingTimeOut,
ContextLines = numContextLines,
ScanUnknownTypes = cliOptions.ScanUnknownTypes,
TagsOnly = tagsOnly,
NoFileMetadata = cliOptions.NoFileMetadata,
AllowAllTagsInBuildFiles = cliOptions.AllowAllTagsInBuildFiles,
MaxNumMatchesPerTag = cliOptions.MaxNumMatchesPerTag,
DisableCrawlArchives = cliOptions.DisableArchiveCrawling,
EnumeratingTimeout = cliOptions.EnumeratingTimeout,
DisableCustomRuleVerification = cliOptions.DisableCustomRuleValidation,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
SuccessErrorCodeOnNoMatches = cliOptions.SuccessErrorCodeOnNoMatches,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, adjustedFactory);
var analyzeResult = command.GetResult();
ResultsWriter writer = new(loggerFactory);
if (cliOptions.NoShowProgressBar)
{
TryWriteResults(writer, analyzeResult, cliOptions, logger);
}
/// <summary>
/// This method gets a logging factory with Console disabled when the progress bar is enabled. Does not change the original options.
/// </summary>
/// <param name="cliOptions">The original options.</param>
/// <returns>A logging factory with adjusted settings.</returns>
private static ILoggerFactory GetAdjustedFactory(CLIAnalyzeCmdOptions cliOptions)
else
{
return new LogOptions()
ProgressBarOptions outputWriterBarOptions = new()
{
ConsoleVerbosityLevel = cliOptions.ConsoleVerbosityLevel,
LogFileLevel = cliOptions.LogFileLevel,
LogFilePath = cliOptions.LogFilePath,
DisableConsoleOutput = !cliOptions.NoShowProgressBar || cliOptions.DisableConsoleOutput
}.GetLoggerFactory();
}
ForegroundColor = ConsoleColor.Yellow,
ForegroundColorDone = ConsoleColor.DarkGreen,
BackgroundColor = ConsoleColor.DarkGray,
ForegroundColorError = ConsoleColor.Red,
BackgroundCharacter = '\u2593',
DisableBottomPercentage = true
};
private static (bool initialSuccess, bool backupSuccess, string? backupLocation) TryWriteResults(ResultsWriter writer, AnalyzeResult analyzeResult, CLIAnalyzeCmdOptions cliOptions, ILogger logger)
{
try
using (IndeterminateProgressBar pbar = new("Writing Result Files.", outputWriterBarOptions))
{
writer.Write(analyzeResult, cliOptions);
return (true, false, null);
}
catch (Exception e)
{
logger.LogError("Exception hit while writing. {Type} : {Message}", e.GetType().Name,
e.Message);
analyzeResult.ResultCode = AnalyzeResult.ExitCode.CriticalError;
// If HTML
if (cliOptions.OutputFileFormat.Equals("html",StringComparison.InvariantCultureIgnoreCase))
var done = false;
(var initialSuccess, var backupSuccess, string? alternatePath) = (false, false, null);
_ = Task.Factory.StartNew(() =>
{
string alternatepath = "backupOutput.json";
var optionsCopy = cliOptions with {OutputFileFormat = "json", OutputFilePath = alternatepath};
try
{
writer.Write(analyzeResult, optionsCopy);
logger.LogInformation("Failed to write HTML report. Exported JSON instead at {AlternatePath}", alternatepath);
return (false, true, alternatepath);
}
catch (Exception e2)
{
logger.LogError("Exception hit while writing backup of result object. {Type} : {Message}", e2.GetType().Name,
e2.Message);
}
}
}
return (false, false, null);
}
(initialSuccess, backupSuccess, alternatePath) = TryWriteResults(writer, analyzeResult, cliOptions,
adjustedFactory.CreateLogger("RunAnalyzeCommand"));
done = true;
});
private static int RunAnalyzeCommand(CLIAnalyzeCmdOptions cliOptions)
{
// If the user manually specified -1 this means they also don't even want the snippet in sarif, so we respect that option
// Otherwise if we are outputting sarif we don't use any of the context information so we set context to 0, if not sarif leave it alone.
bool isSarif = cliOptions.OutputFileFormat.Equals("sarif", StringComparison.InvariantCultureIgnoreCase);
int numContextLines = cliOptions.ContextLines == -1 ? cliOptions.ContextLines : isSarif ? 0 : cliOptions.ContextLines;
// tagsOnly isn't compatible with sarif output, we choose to prioritize the choice of sarif.
bool tagsOnly = !isSarif && cliOptions.TagsOnly;
var logger = cliOptions.GetLoggerFactory().CreateLogger("Program");
if (!cliOptions.NoShowProgressBar)
{
if (string.IsNullOrEmpty(cliOptions.LogFilePath))
{
logger.LogInformation("Progress bar is enabled so console output will be suppressed. No LogFilePath has been configured so you will not receive log messages");
}
else
{
logger.LogInformation("Progress bar is enabled so console output will be suppressed. For log messages check the log at {LogPath}", cliOptions.LogFilePath);
}
}
ILoggerFactory adjustedFactory = GetAdjustedFactory(cliOptions);
AnalyzeCommand command = new(new AnalyzeOptions()
{
SourcePath = cliOptions.SourcePath ?? Array.Empty<string>(),
CustomRulesPath = cliOptions.CustomRulesPath ?? "",
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
IgnoreDefaultRules = cliOptions.IgnoreDefaultRules,
ConfidenceFilters = cliOptions.ConfidenceFilters,
SeverityFilters = cliOptions.SeverityFilters,
FilePathExclusions = cliOptions.FilePathExclusions,
SingleThread = cliOptions.SingleThread,
NoShowProgress = cliOptions.NoShowProgressBar,
FileTimeOut = cliOptions.FileTimeOut,
ProcessingTimeOut = cliOptions.ProcessingTimeOut,
ContextLines = numContextLines,
ScanUnknownTypes = cliOptions.ScanUnknownTypes,
TagsOnly = tagsOnly,
NoFileMetadata = cliOptions.NoFileMetadata,
AllowAllTagsInBuildFiles = cliOptions.AllowAllTagsInBuildFiles,
MaxNumMatchesPerTag = cliOptions.MaxNumMatchesPerTag,
DisableCrawlArchives = cliOptions.DisableArchiveCrawling,
EnumeratingTimeout = cliOptions.EnumeratingTimeout,
DisableCustomRuleVerification = cliOptions.DisableCustomRuleValidation,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
SuccessErrorCodeOnNoMatches = cliOptions.SuccessErrorCodeOnNoMatches,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, adjustedFactory);
while (!done) Thread.Sleep(100);
AnalyzeResult analyzeResult = command.GetResult();
ResultsWriter writer = new(loggerFactory);
if (cliOptions.NoShowProgressBar)
{
TryWriteResults(writer, analyzeResult, cliOptions, logger);
}
else
{
ProgressBarOptions outputWriterBarOptions = new()
{
ForegroundColor = ConsoleColor.Yellow,
ForegroundColorDone = ConsoleColor.DarkGreen,
BackgroundColor = ConsoleColor.DarkGray,
ForegroundColorError = ConsoleColor.Red,
BackgroundCharacter = '\u2593',
DisableBottomPercentage = true
};
using (IndeterminateProgressBar pbar = new("Writing Result Files.", outputWriterBarOptions))
{
var done = false;
(bool initialSuccess, bool backupSuccess, string? alternatePath) = (false, false, null);
_ = Task.Factory.StartNew(() =>
{
(initialSuccess, backupSuccess, alternatePath) = TryWriteResults(writer, analyzeResult, cliOptions, adjustedFactory.CreateLogger("RunAnalyzeCommand"));
done = true;
});
while (!done)
{
Thread.Sleep(100);
}
pbar.ObservedError = !initialSuccess;
pbar.Message = !initialSuccess ?
(backupSuccess ? $"Failed to write results. Wrote backup results to {alternatePath}." : "Failed to write Results and failed to write Backup Results.") : $"Results written to {cliOptions.OutputFilePath}.";
pbar.Finished();
}
Console.Write(Environment.NewLine);
pbar.ObservedError = !initialSuccess;
pbar.Message = !initialSuccess
? backupSuccess
? $"Failed to write results. Wrote backup results to {alternatePath}."
: "Failed to write Results and failed to write Backup Results."
: $"Results written to {cliOptions.OutputFilePath}.";
pbar.Finished();
}
return (int)analyzeResult.ResultCode;
Console.Write(Environment.NewLine);
}
private static int RunTagDiffCommand(CLITagDiffCmdOptions cliOptions)
return (int)analyzeResult.ResultCode;
}
private static int RunTagDiffCommand(CLITagDiffCmdOptions cliOptions)
{
TagDiffCommand command = new(new TagDiffOptions
{
TagDiffCommand command = new(new TagDiffOptions()
{
SourcePath1 = cliOptions.SourcePath1,
SourcePath2 = cliOptions.SourcePath2,
CustomRulesPath = cliOptions.CustomRulesPath,
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
IgnoreDefaultRules = cliOptions.IgnoreDefaultRules,
FilePathExclusions = cliOptions.FilePathExclusions,
TestType = cliOptions.TestType,
ConfidenceFilters = cliOptions.ConfidenceFilters,
SeverityFilters = cliOptions.SeverityFilters,
FileTimeOut = cliOptions.FileTimeOut,
ProcessingTimeOut = cliOptions.ProcessingTimeOut,
ScanUnknownTypes = cliOptions.ScanUnknownTypes,
SingleThread = cliOptions.SingleThread,
DisableCustomRuleValidation = cliOptions.DisableCustomRuleValidation,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
SuccessErrorCodeOnNoMatches = cliOptions.SuccessErrorCodeOnNoMatches,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, loggerFactory);
SourcePath1 = cliOptions.SourcePath1,
SourcePath2 = cliOptions.SourcePath2,
CustomRulesPath = cliOptions.CustomRulesPath,
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
IgnoreDefaultRules = cliOptions.IgnoreDefaultRules,
FilePathExclusions = cliOptions.FilePathExclusions,
TestType = cliOptions.TestType,
ConfidenceFilters = cliOptions.ConfidenceFilters,
SeverityFilters = cliOptions.SeverityFilters,
FileTimeOut = cliOptions.FileTimeOut,
ProcessingTimeOut = cliOptions.ProcessingTimeOut,
ScanUnknownTypes = cliOptions.ScanUnknownTypes,
SingleThread = cliOptions.SingleThread,
DisableCustomRuleValidation = cliOptions.DisableCustomRuleValidation,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
SuccessErrorCodeOnNoMatches = cliOptions.SuccessErrorCodeOnNoMatches,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, loggerFactory);
TagDiffResult tagDiffResult = command.GetResult();
var tagDiffResult = command.GetResult();
ResultsWriter writer = new(loggerFactory);
writer.Write(tagDiffResult, cliOptions);
ResultsWriter writer = new(loggerFactory);
writer.Write(tagDiffResult, cliOptions);
return (int)tagDiffResult.ResultCode;
}
private static int RunExportTagsCommand(CLIExportTagsCmdOptions cliOptions)
return (int)tagDiffResult.ResultCode;
}
private static int RunExportTagsCommand(CLIExportTagsCmdOptions cliOptions)
{
ExportTagsCommand command = new(new ExportTagsOptions
{
ExportTagsCommand command = new(new ExportTagsOptions()
{
IgnoreDefaultRules = cliOptions.IgnoreDefaultRules,
CustomRulesPath = cliOptions.CustomRulesPath,
}, loggerFactory);
IgnoreDefaultRules = cliOptions.IgnoreDefaultRules,
CustomRulesPath = cliOptions.CustomRulesPath
}, loggerFactory);
ExportTagsResult exportTagsResult = command.GetResult();
var exportTagsResult = command.GetResult();
ResultsWriter writer = new(loggerFactory);
writer.Write(exportTagsResult, cliOptions);
ResultsWriter writer = new(loggerFactory);
writer.Write(exportTagsResult, cliOptions);
return (int)exportTagsResult.ResultCode;
}
return (int)exportTagsResult.ResultCode;
}
private static int RunVerifyRulesCommand(CLIVerifyRulesCmdOptions cliOptions)
private static int RunVerifyRulesCommand(CLIVerifyRulesCmdOptions cliOptions)
{
VerifyRulesCommand command = new(new VerifyRulesOptions
{
VerifyRulesCommand command = new(new VerifyRulesOptions()
{
VerifyDefaultRules = cliOptions.VerifyDefaultRules,
CustomRulesPath = cliOptions.CustomRulesPath,
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, loggerFactory);
VerifyDefaultRules = cliOptions.VerifyDefaultRules,
CustomRulesPath = cliOptions.CustomRulesPath,
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, loggerFactory);
VerifyRulesResult exportTagsResult = command.GetResult();
var verifyRulesResult = command.GetResult();
ResultsWriter writer = new(loggerFactory);
writer.Write(exportTagsResult, cliOptions);
ResultsWriter writer = new(loggerFactory);
writer.Write(verifyRulesResult, cliOptions);
return (int)exportTagsResult.ResultCode;
}
return (int)verifyRulesResult.ResultCode;
}
private static int RunPackRulesCommand(CLIPackRulesCmdOptions cliOptions)
private static int RunPackRulesCommand(CLIPackRulesCmdOptions cliOptions)
{
PackRulesCommand command = new(new PackRulesOptions
{
PackRulesCommand command = new(new PackRulesOptions()
{
CustomRulesPath = cliOptions.CustomRulesPath,
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
PackEmbeddedRules = cliOptions.PackEmbeddedRules
}, loggerFactory);
CustomRulesPath = cliOptions.CustomRulesPath,
CustomCommentsPath = cliOptions.CustomCommentsPath,
CustomLanguagesPath = cliOptions.CustomLanguagesPath,
PackEmbeddedRules = cliOptions.PackEmbeddedRules,
DisableRequireUniqueIds = cliOptions.DisableRequireUniqueIds,
RequireMustMatch = cliOptions.RequireMustMatch,
RequireMustNotMatch = cliOptions.RequireMustNotMatch
}, loggerFactory);
PackRulesResult exportTagsResult = command.GetResult();
var packRulesResult = command.GetResult();
ResultsWriter writer = new(loggerFactory);
writer.Write(exportTagsResult, cliOptions);
return (int)exportTagsResult.ResultCode;
}
ResultsWriter writer = new(loggerFactory);
writer.Write(packRulesResult, cliOptions);
return (int)packRulesResult.ResultCode;
}
}

Просмотреть файл

@ -3,15 +3,15 @@
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>bin\Release\netcoreapp3.1\publish\</PublishDir>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
</PropertyGroup>
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishDir>bin\Release\netcoreapp3.1\publish\</PublishDir>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
</PropertyGroup>
</Project>

Просмотреть файл

@ -1,92 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.ApplicationInspector.CLI
using System;
using System.IO;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
namespace Microsoft.ApplicationInspector.CLI;
/// <summary>
/// Wrapper for CLI only output arg validation which may be unique to a command
/// and for allocating the correct writter type and format writter object
/// </summary>
public class ResultsWriter
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
public ResultsWriter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ResultsWriter>();
_loggerFactory = loggerFactory;
}
public void Write(Result result, CLICommandOptions options)
{
var writerFactory = new WriterFactory(_loggerFactory);
var writer = writerFactory.GetWriter(options);
string commandCompletedMsg;
//perform type checking and assign final msg string
if (result is TagDiffResult)
{
commandCompletedMsg = "Tag Diff";
}
else if (result is ExportTagsResult)
{
commandCompletedMsg = "Export Tags";
}
else if (result is VerifyRulesResult)
{
commandCompletedMsg = "Verify Rules";
}
else if (result is PackRulesResult)
{
commandCompletedMsg = "Pack Rules";
}
else if (result is AnalyzeResult analyzeResult &&
options is CLIAnalyzeCmdOptions cLIAnalyzeCmdOptions) //special handling for html format
{
commandCompletedMsg = "Analyze";
//additional prechecks required for analyze html format
if (writer is AnalyzeHtmlWriter)
{
var MAX_HTML_REPORT_FILE_SIZE = 1024 * 1000 * 3; //warn about potential slow rendering
writer.WriteResults(analyzeResult, cLIAnalyzeCmdOptions);
//post checks
if (options.OutputFilePath is not null && File.Exists(options.OutputFilePath) &&
new FileInfo(options.OutputFilePath).Length > MAX_HTML_REPORT_FILE_SIZE)
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.ANALYZE_REPORTSIZE_WARN));
Finalize(writer, "Analyze");
return;
}
}
else
{
throw new Exception("Unrecognized object types for write results");
}
//general for all but analyze html format
writer.WriteResults(result, options);
Finalize(writer, commandCompletedMsg);
}
/// <summary>
/// Wrapper for CLI only output arg validation which may be unique to a command
/// and for allocating the correct writter type and format writter object
/// Allow for final actions if even and common file path notice to console
/// Most Writer.Write operations flushandclose the stream automatically but .Flush
/// </summary>
public class ResultsWriter
/// <param name="_outputWriter"></param>
/// <param name="options"></param>
internal void Finalize(CommandResultsWriter? outputWriter, string commandName)
{
public ResultsWriter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ResultsWriter>();
_loggerFactory = loggerFactory;
}
private ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
public void Write(Result result, CLICommandOptions options)
{
WriterFactory writerFactory = new WriterFactory(_loggerFactory);
CommandResultsWriter? writer = writerFactory.GetWriter(options);
string commandCompletedMsg;
//perform type checking and assign final msg string
if (result is TagDiffResult)
{
commandCompletedMsg = "Tag Diff";
}
else if (result is ExportTagsResult)
{
commandCompletedMsg = "Export Tags";
}
else if (result is VerifyRulesResult)
{
commandCompletedMsg = "Verify Rules";
}
else if (result is PackRulesResult)
{
commandCompletedMsg = "Pack Rules";
}
else if (result is AnalyzeResult analyzeResult && options is CLIAnalyzeCmdOptions cLIAnalyzeCmdOptions) //special handling for html format
{
commandCompletedMsg = "Analyze";
//additional prechecks required for analyze html format
if (writer is AnalyzeHtmlWriter)
{
int MAX_HTML_REPORT_FILE_SIZE = 1024 * 1000 * 3; //warn about potential slow rendering
writer.WriteResults(analyzeResult, cLIAnalyzeCmdOptions);
//post checks
if (options.OutputFilePath is not null && File.Exists(options.OutputFilePath) && new FileInfo(options.OutputFilePath).Length > MAX_HTML_REPORT_FILE_SIZE)
{
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.ANALYZE_REPORTSIZE_WARN));
}
Finalize(writer, "Analyze");
return;
}
}
else
{
throw new Exception("Unrecognized object types for write results");
}
//general for all but analyze html format
writer.WriteResults(result, options);
Finalize(writer, commandCompletedMsg);
}
/// <summary>
/// Allow for final actions if even and common file path notice to console
/// Most Writer.Write operations flushandclose the stream automatically but .Flush
/// </summary>
/// <param name="_outputWriter"></param>
/// <param name="options"></param>
internal void Finalize(CommandResultsWriter? outputWriter, string commandName)
{
_logger.LogInformation(MsgHelp.FormatString(MsgHelp.ID.CMD_COMPLETED, commandName));
}
_logger.LogInformation(MsgHelp.FormatString(MsgHelp.ID.CMD_COMPLETED, commandName));
}
}

Просмотреть файл

@ -1,160 +1,150 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.ApplicationInspector.Commands
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using DotLiquid;
using Microsoft.ApplicationInspector.RulesEngine;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.Commands;
/// <summary>
/// Root parent for tag group preferences file and used in Writers\AnalyzeHtmlWriter.cs
/// </summary>
public class TagCategory
{
using DotLiquid;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
/// <summary>
/// Root parent for tag group preferences file and used in Writers\AnalyzeHtmlWriter.cs
/// </summary>
public class TagCategory
public enum tagInfoType
{
public enum tagInfoType { uniqueTags, allTags };
uniqueTags,
allTags
}
[JsonProperty(PropertyName = "type")]
public tagInfoType Type;
[JsonProperty(PropertyName = "type")] public tagInfoType Type;
[JsonProperty(PropertyName = "categoryName")]
public string? Name { get; set; }
public TagCategory()
{
Groups = new List<TagGroup>();
}
[JsonProperty(PropertyName = "groups")]
public List<TagGroup>? Groups { get; set; }
[JsonProperty(PropertyName = "categoryName")]
public string? Name { get; set; }
public TagCategory()
[JsonProperty(PropertyName = "groups")]
public List<TagGroup>? Groups { get; set; }
}
/// <summary>
/// Used to read customizable preference for Profile page e.g. rules\profile\profile.json
/// </summary>
public class TagGroup : Drop
{
public TagGroup()
{
Patterns = new List<TagSearchPattern>();
}
[JsonProperty(PropertyName = "title")] public string? Title { get; set; }
[JsonIgnore] public string? IconURL { get; set; }
[JsonProperty(PropertyName = "dataRef")]
public string? DataRef { get; set; }
[JsonProperty(PropertyName = "patterns")]
public List<TagSearchPattern>? Patterns { get; set; }
}
public class TagSearchPattern : Drop
{
private Regex? _expression;
private string _searchPattern = "";
[JsonProperty(PropertyName = "searchPattern")]
public string SearchPattern
{
get => _searchPattern;
set
{
Groups = new List<TagGroup>();
_searchPattern = value;
_expression = null;
}
}
/// <summary>
/// Used to read customizable preference for Profile page e.g. rules\profile\profile.json
/// </summary>
public class TagGroup : Drop
public Regex Expression
{
[JsonProperty(PropertyName = "title")]
public string? Title { get; set; }
[JsonIgnore]
public string? IconURL { get; set; }
[JsonProperty(PropertyName = "dataRef")]
public string? DataRef { get; set; }
[JsonProperty(PropertyName = "patterns")]
public List<TagSearchPattern>? Patterns { get; set; }
public TagGroup()
get
{
Patterns = new List<TagSearchPattern>();
if (_expression == null)
_expression = new Regex(SearchPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
return _expression;
}
}
public class TagSearchPattern : Drop
[JsonProperty(PropertyName = "displayName")]
public string? DisplayName { get; set; }
[JsonProperty(PropertyName = "detectedIcon")]
public string? DetectedIcon { get; set; } = "fas fa-cat"; //default
[JsonProperty(PropertyName = "notDetectedIcon")]
public string? NotDetectedIcon { get; set; }
[JsonProperty(PropertyName = "detected")]
public bool Detected { get; set; }
[JsonProperty(PropertyName = "details")]
public string Details
{
private string _searchPattern = "";
private Regex? _expression;
[JsonProperty(PropertyName = "searchPattern")]
public string SearchPattern
get
{
get
{
return _searchPattern;
}
set
{
_searchPattern = value;
_expression = null;
}
var result = Detected ? "View" : "N/A";
return result;
}
public static bool ShouldSerializeExpression()
{
return false;
}
public Regex Expression
{
get
{
if (_expression == null)
{
_expression = new Regex(SearchPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
return _expression;
}
}
[JsonProperty(PropertyName = "displayName")]
public string? DisplayName { get; set; }
[JsonProperty(PropertyName = "detectedIcon")]
public string? DetectedIcon { get; set; } = "fas fa-cat";//default
[JsonProperty(PropertyName = "notDetectedIcon")]
public string? NotDetectedIcon { get; set; }
[JsonProperty(PropertyName = "detected")]
public bool Detected { get; set; }
[JsonProperty(PropertyName = "details")]
public string Details
{
get
{
string result = Detected ? "View" : "N/A";
return result;
}
}
[JsonProperty(PropertyName = "confidence")]
public string Confidence { get; set; } = "Medium";
}
/// <summary>
/// Primary use is development of lists of tags with specific group or pattern properties in reporting
/// </summary>
public class TagInfo : Drop
[JsonProperty(PropertyName = "confidence")]
public string Confidence { get; set; } = "Medium";
public static bool ShouldSerializeExpression()
{
[JsonProperty(PropertyName = "tag")]
public string? Tag { get; set; }
return false;
}
}
[JsonProperty(PropertyName = "displayName")]
public string? ShortTag { get; set; }
/// <summary>
/// Primary use is development of lists of tags with specific group or pattern properties in reporting
/// </summary>
public class TagInfo : Drop
{
private string _confidence = "Medium";
[JsonIgnore]
public string? StatusIcon { get; set; }
[JsonProperty(PropertyName = "tag")] public string? Tag { get; set; }
private string _confidence = "Medium";
[JsonProperty(PropertyName = "displayName")]
public string? ShortTag { get; set; }
[JsonProperty(PropertyName = "confidence")]
public string Confidence
[JsonIgnore] public string? StatusIcon { get; set; }
[JsonProperty(PropertyName = "confidence")]
public string Confidence
{
get => _confidence;
set
{
get => _confidence;
set
{
if (Enum.TryParse(value, true, out RulesEngine.Confidence test))
{
_confidence = value;
}
}
if (Enum.TryParse(value, true, out Confidence test)) _confidence = value;
}
[JsonProperty(PropertyName = "severity")]
public string Severity { get; set; } = "Moderate";
[JsonProperty(PropertyName = "detected")]
public bool Detected { get; set; }
}
public class TagException
{
[JsonProperty(PropertyName = "tag")]
public string? Tag { get; set; }
}
[JsonProperty(PropertyName = "severity")]
public string Severity { get; set; } = "Moderate";
[JsonProperty(PropertyName = "detected")]
public bool Detected { get; set; }
}
public class TagException
{
[JsonProperty(PropertyName = "tag")] public string? Tag { get; set; }
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,53 +1,46 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
using System.IO;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.CLI;
/// <summary>
/// Writes in json format
/// Users can select arguments to filter output to 1. only simple tags 2. only matchlist without rollup metadata etc.
/// 3. everything
/// Lists of tagreportgroups are written as well as match list details so users have chose to present the same
/// UI as shown in the HTML report to the level of detail desired...
/// </summary>
public class AnalyzeJsonWriter : CommandResultsWriter
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System.IO;
private readonly ILogger<AnalyzeJsonWriter> _logger;
public AnalyzeJsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
_logger = loggerFactory?.CreateLogger<AnalyzeJsonWriter>() ?? NullLogger<AnalyzeJsonWriter>.Instance;
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
var analyzeResult = (AnalyzeResult)result;
JsonSerializer jsonSerializer = new();
jsonSerializer.Formatting = Formatting.Indented;
if (TextWriter != null) jsonSerializer.Serialize(TextWriter, analyzeResult);
if (autoClose) FlushAndClose();
}
/// <summary>
/// Writes in json format
/// Users can select arguments to filter output to 1. only simple tags 2. only matchlist without rollup metadata etc. 3. everything
/// Lists of tagreportgroups are written as well as match list details so users have chose to present the same
/// UI as shown in the HTML report to the level of detail desired...
/// simple wrapper for serializing results for simple tags only during processing
/// </summary>
public class AnalyzeJsonWriter : CommandResultsWriter
private class TagsFile
{
private readonly ILogger<AnalyzeJsonWriter> _logger;
public AnalyzeJsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
_logger = loggerFactory?.CreateLogger<AnalyzeJsonWriter>() ?? NullLogger<AnalyzeJsonWriter>.Instance;
}
/// <summary>
/// simple wrapper for serializing results for simple tags only during processing
/// </summary>
private class TagsFile
{
[JsonProperty(PropertyName = "tags")]
public string[]? Tags { get; set; }
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
AnalyzeResult analyzeResult = (AnalyzeResult)result;
JsonSerializer jsonSerializer = new();
jsonSerializer.Formatting = Formatting.Indented;
if (TextWriter != null)
{
jsonSerializer.Serialize(TextWriter, analyzeResult);
}
if (autoClose)
{
FlushAndClose();
}
}
[JsonProperty(PropertyName = "tags")] public string[]? Tags { get; set; }
}
}

Просмотреть файл

@ -1,168 +1,158 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.CodeAnalysis.Sarif;
using Microsoft.CST.OAT.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using Location = Microsoft.CodeAnalysis.Sarif.Location;
using Result = Microsoft.ApplicationInspector.Commands.Result;
namespace Microsoft.ApplicationInspector.CLI;
internal static class AnalyzeSarifWriterExtensions
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.CodeAnalysis.Sarif;
using Microsoft.CST.OAT.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Location = Microsoft.CodeAnalysis.Sarif.Location;
using Result = Microsoft.ApplicationInspector.Commands.Result;
internal static class AnalyzeSarifWriterExtensions
internal static void AddRange(this TagsCollection tc, IEnumerable<string>? tagsToAdd)
{
internal static void AddRange(this TagsCollection tc, IEnumerable<string>? tagsToAdd)
{
if (tagsToAdd is null) return;
foreach (string tag in tagsToAdd)
{
tc.Add(tag);
}
}
if (tagsToAdd is null) return;
foreach (var tag in tagsToAdd) tc.Add(tag);
}
/// <summary>
/// Writes in sarif format
/// </summary>
public class AnalyzeSarifWriter : CommandResultsWriter
}
/// <summary>
/// Writes in sarif format
/// </summary>
public class AnalyzeSarifWriter : CommandResultsWriter
{
private readonly ILogger<AnalyzeSarifWriter> _logger;
public AnalyzeSarifWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
private readonly ILogger<AnalyzeSarifWriter> _logger;
_logger = loggerFactory?.CreateLogger<AnalyzeSarifWriter>() ?? NullLogger<AnalyzeSarifWriter>.Instance;
}
public AnalyzeSarifWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
if (TextWriter is null) throw new NullReferenceException(nameof(TextWriter));
string? basePath = null;
if (commandOptions is CLIAnalyzeCmdOptions cLIAnalyzeCmdOptions)
{
_logger = loggerFactory?.CreateLogger<AnalyzeSarifWriter>() ?? NullLogger<AnalyzeSarifWriter>.Instance;
}
basePath = cLIAnalyzeCmdOptions.BasePath;
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
if (TextWriter is null)
if (result is AnalyzeResult analyzeResult)
{
throw new NullReferenceException(nameof(TextWriter));
}
string? basePath = null;
if (commandOptions is CLIAnalyzeCmdOptions cLIAnalyzeCmdOptions)
{
basePath = cLIAnalyzeCmdOptions.BasePath;
SarifLog log = new();
var sarifVersion = SarifVersion.Current;
log.SchemaUri = sarifVersion.ConvertToSchemaUri();
log.Version = sarifVersion;
log.Runs = new List<Run>();
var run = new Run();
if (result is AnalyzeResult analyzeResult)
{
SarifLog log = new();
SarifVersion sarifVersion = SarifVersion.Current;
log.SchemaUri = sarifVersion.ConvertToSchemaUri();
log.Version = sarifVersion;
log.Runs = new List<Run>();
var run = new Run();
if (Uri.TryCreate(cLIAnalyzeCmdOptions.RepositoryUri, UriKind.RelativeOrAbsolute, out Uri? uri))
if (Uri.TryCreate(cLIAnalyzeCmdOptions.RepositoryUri, UriKind.RelativeOrAbsolute, out var uri))
run.VersionControlProvenance = new List<VersionControlDetails>
{
run.VersionControlProvenance = new List<VersionControlDetails>()
new()
{
new VersionControlDetails()
{
RepositoryUri = uri,
RevisionId = cLIAnalyzeCmdOptions.CommitHash
}
};
}
var artifacts = new List<Artifact>();
run.Tool = new Tool
{
Driver = new ToolComponent
{
Name = $"Application Inspector",
InformationUri = new Uri("https://github.com/microsoft/ApplicationInspector/"),
Organization = "Microsoft",
Version = Helpers.GetVersionString(),
RepositoryUri = uri,
RevisionId = cLIAnalyzeCmdOptions.CommitHash
}
};
var reportingDescriptors = new List<ReportingDescriptor>();
run.Results = new List<CodeAnalysis.Sarif.Result>();
foreach (var match in analyzeResult.Metadata.Matches)
var artifacts = new List<Artifact>();
run.Tool = new Tool
{
Driver = new ToolComponent
{
var sarifResult = new CodeAnalysis.Sarif.Result();
Name = "Application Inspector",
InformationUri = new Uri("https://github.com/microsoft/ApplicationInspector/"),
Organization = "Microsoft",
Version = Helpers.GetVersionString()
}
};
var reportingDescriptors = new List<ReportingDescriptor>();
run.Results = new List<CodeAnalysis.Sarif.Result>();
foreach (var match in analyzeResult.Metadata.Matches)
{
var sarifResult = new CodeAnalysis.Sarif.Result();
if (match.Rule is not null)
if (match.Rule is not null)
{
if (!reportingDescriptors.Any(r => r.Id == match.Rule.Id))
{
if (!reportingDescriptors.Any(r => r.Id == match.Rule.Id))
ReportingDescriptor reportingDescriptor = new()
{
ReportingDescriptor reportingDescriptor = new()
FullDescription = new MultiformatMessageString { Text = match.Rule.Description },
Id = match.Rule.Id,
Name = match.Rule.Name,
DefaultConfiguration = new ReportingConfiguration
{
FullDescription = new MultiformatMessageString() { Text = match.Rule.Description },
Id = match.Rule.Id,
Name = match.Rule.Name,
DefaultConfiguration = new ReportingConfiguration()
{
Level = GetSarifFailureLevel(match.Rule.Severity)
}
};
reportingDescriptor.Tags.AddRange(match.Rule.Tags);
reportingDescriptors.Add(reportingDescriptor);
}
sarifResult.Level = GetSarifFailureLevel(match.Rule.Severity);
sarifResult.RuleId = match.Rule.Id;
sarifResult.Tags.AddRange(match.Rule.Tags);
sarifResult.Message = new Message()
{
Text = match.Rule.Description
};
if (match.FileName is not null)
{
string fileName = match.FileName;
if (basePath is not null)
{
fileName = Path.GetRelativePath(basePath, fileName);
Level = GetSarifFailureLevel(match.Rule.Severity)
}
if (Uri.TryCreate(fileName, UriKind.RelativeOrAbsolute, out Uri? outUri))
};
reportingDescriptor.Tags.AddRange(match.Rule.Tags);
reportingDescriptors.Add(reportingDescriptor);
}
sarifResult.Level = GetSarifFailureLevel(match.Rule.Severity);
sarifResult.RuleId = match.Rule.Id;
sarifResult.Tags.AddRange(match.Rule.Tags);
sarifResult.Message = new Message
{
Text = match.Rule.Description
};
if (match.FileName is not null)
{
var fileName = match.FileName;
if (basePath is not null) fileName = Path.GetRelativePath(basePath, fileName);
if (Uri.TryCreate(fileName, UriKind.RelativeOrAbsolute, out var outUri))
{
var artifactIndex = artifacts.FindIndex(a => a.Location.Uri.Equals(outUri));
if (artifactIndex == -1)
{
int artifactIndex = artifacts.FindIndex(a => a.Location.Uri.Equals(outUri));
if (artifactIndex == -1)
Artifact artifact = new()
{
Artifact artifact = new()
Location = new ArtifactLocation
{
Location = new ArtifactLocation()
{
Index = artifacts.Count,
Uri = outUri
},
};
artifactIndex = artifact.Location.Index;
artifact.Tags.AddRange(match.Rule.Tags);
if (match.LanguageInfo is { } languageInfo)
{
artifact.SourceLanguage = languageInfo.Name;
Index = artifacts.Count,
Uri = outUri
}
artifacts.Add(artifact);
}
else
{
artifacts[artifactIndex].Tags.AddRange(match.Rule.Tags);
}
sarifResult.Locations = new List<Location>()
};
artifactIndex = artifact.Location.Index;
artifact.Tags.AddRange(match.Rule.Tags);
if (match.LanguageInfo is { } languageInfo)
artifact.SourceLanguage = languageInfo.Name;
artifacts.Add(artifact);
}
else
{
new Location()
artifacts[artifactIndex].Tags.AddRange(match.Rule.Tags);
}
sarifResult.Locations = new List<Location>
{
new()
{
PhysicalLocation = new PhysicalLocation()
PhysicalLocation = new PhysicalLocation
{
ArtifactLocation = new ArtifactLocation()
ArtifactLocation = new ArtifactLocation
{
Index = artifactIndex
},
Region = new Region()
Region = new Region
{
StartLine = match.StartLocationLine,
StartColumn = match.StartLocationColumn,
EndLine = match.EndLocationLine,
EndColumn = match.EndLocationColumn,
Snippet = new ArtifactContent()
Snippet = new ArtifactContent
{
Text = match.Sample
}
@ -170,39 +160,42 @@ namespace Microsoft.ApplicationInspector.CLI
}
}
};
}
}
}
run.Artifacts = artifacts;
run.Tool.Driver.Rules = reportingDescriptors;
run.Results.Add(sarifResult);
}
log.Runs.Add(run);
JsonSerializerSettings serializerSettings = new();
var serializer = new JsonSerializer();
serializer.Serialize(TextWriter, log);
FlushAndClose();
}
else
{
throw new ArgumentException("This writer can only write Analyze results.", nameof(result));
run.Artifacts = artifacts;
run.Tool.Driver.Rules = reportingDescriptors;
run.Results.Add(sarifResult);
}
log.Runs.Add(run);
JsonSerializerSettings serializerSettings = new();
var serializer = new JsonSerializer();
serializer.Serialize(TextWriter, log);
FlushAndClose();
}
else
{
throw new ArgumentException("This writer requires a CLIAnalyzeCmdOptions options argument.", nameof(commandOptions));
throw new ArgumentException("This writer can only write Analyze results.", nameof(result));
}
}
private static FailureLevel GetSarifFailureLevel(RulesEngine.Severity severity) => severity switch
else
{
RulesEngine.Severity.BestPractice => FailureLevel.Note,
RulesEngine.Severity.Critical => FailureLevel.Error,
RulesEngine.Severity.Important => FailureLevel.Warning,
RulesEngine.Severity.ManualReview => FailureLevel.Note,
RulesEngine.Severity.Moderate => FailureLevel.Warning,
throw new ArgumentException("This writer requires a CLIAnalyzeCmdOptions options argument.",
nameof(commandOptions));
}
}
private static FailureLevel GetSarifFailureLevel(Severity severity)
{
return severity switch
{
Severity.BestPractice => FailureLevel.Note,
Severity.Critical => FailureLevel.Error,
Severity.Important => FailureLevel.Warning,
Severity.ManualReview => FailureLevel.Note,
Severity.Moderate => FailureLevel.Warning,
_ => FailureLevel.Note
};
}

Просмотреть файл

@ -1,186 +1,153 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.CLI;
public class AnalyzeTextWriter : CommandResultsWriter
{
using System.Linq;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
private readonly string _formatString;
private readonly ILogger<AnalyzeJsonWriter> _logger;
private readonly int COLUMN_MAX = 80;
public class AnalyzeTextWriter : CommandResultsWriter
public AnalyzeTextWriter(TextWriter textWriter, string formatString, ILoggerFactory? loggerFactory = null) :
base(textWriter)
{
private readonly int COLUMN_MAX = 80;
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
_logger = loggerFactory?.CreateLogger<AnalyzeJsonWriter>() ?? NullLogger<AnalyzeJsonWriter>.Instance;
if (string.IsNullOrEmpty(formatString))
_formatString =
"Tag:%T,Rule:%N,Ruleid:%R,Confidence:%X,File:%F,Language:%l,SourceType:%tLine:%L,%C,Sample:%m";
else
_formatString = formatString;
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
var cLIAnalyzeCmdOptions = (CLIAnalyzeCmdOptions)commandOptions;
var analyzeResult = (AnalyzeResult)result;
if (TextWriter is null) throw new ArgumentNullException(nameof(TextWriter));
TextWriter.WriteLine("Results");
WriteAppMeta(analyzeResult.Metadata);
WriteDependencies(analyzeResult.Metadata);
TextWriter.WriteLine(MakeHeading("Match Details"));
foreach (var match in analyzeResult.Metadata.Matches ?? new List<MatchRecord>()) WriteMatch(match);
if (autoClose) FlushAndClose();
}
private string StringList(IEnumerable<string> data)
{
return string.Join(' ', data);
}
private string StringList(IDictionary<string, int> data)
{
return string.Join(' ', data.Keys);
}
private string StringList(IDictionary<string, string> data)
{
StringBuilder build = new();
foreach (var s in data.Values)
{
CLIAnalyzeCmdOptions cLIAnalyzeCmdOptions = (CLIAnalyzeCmdOptions)commandOptions;
AnalyzeResult analyzeResult = (AnalyzeResult)result;
if (TextWriter is null)
{
throw new ArgumentNullException(nameof(TextWriter));
}
TextWriter.WriteLine("Results");
WriteAppMeta(analyzeResult.Metadata);
WriteDependencies(analyzeResult.Metadata);
TextWriter.WriteLine(MakeHeading("Match Details"));
foreach (MatchRecord match in analyzeResult.Metadata.Matches ?? new List<MatchRecord>())
{
WriteMatch(match);
}
if (autoClose)
{
FlushAndClose();
}
build.Append(s);
build.Append(" ");
}
public AnalyzeTextWriter(TextWriter textWriter, string formatString, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
_logger = loggerFactory?.CreateLogger<AnalyzeJsonWriter>() ?? NullLogger<AnalyzeJsonWriter>.Instance;
if (string.IsNullOrEmpty(formatString))
{
_formatString = "Tag:%T,Rule:%N,Ruleid:%R,Confidence:%X,File:%F,Language:%l,SourceType:%tLine:%L,%C,Sample:%m";
}
else
{
_formatString = formatString;
}
}
return build.ToString();
}
private string StringList(IEnumerable<string> data)
{
return string.Join(' ', data);
}
/// <summary>
/// even out delineator for headings
/// </summary>
/// <param name="header"></param>
/// <returns></returns>
private string MakeHeading(string header)
{
StringBuilder build = new();
build.Append(string.Format("[{0}]", header));
for (var i = header.Length; i < COLUMN_MAX; i++) build.Append("-");
private string StringList(IDictionary<string, int> data)
{
return string.Join(' ', data.Keys);
}
return build.ToString();
}
private string StringList(IDictionary<string, string> data)
{
StringBuilder build = new();
foreach (string s in data.Values)
{
build.Append(s);
build.Append(" ");
}
public void WriteAppMeta(MetaData metaData)
{
if (TextWriter is null) throw new ArgumentNullException(nameof(TextWriter));
//write predefined characteristics
TextWriter.WriteLine(string.Format(MakeHeading("Project Info")));
TextWriter.WriteLine($"Name: {metaData.ApplicationName + " " + metaData.SourceVersion}");
TextWriter.WriteLine($"Description: {metaData.Description}");
TextWriter.WriteLine($"Source path: {metaData.SourcePath}");
TextWriter.WriteLine($"Authors: {metaData.Authors}");
TextWriter.WriteLine($"Last Updated: {metaData.LastUpdated}");
TextWriter.WriteLine(
$"Languages: {(metaData.Languages is not null ? StringList(metaData.Languages) : string.Empty)}");
TextWriter.WriteLine(string.Format(MakeHeading("Scan Settings")));
TextWriter.WriteLine($"Date scanned: {metaData.DateScanned}");
TextWriter.WriteLine(string.Format(MakeHeading("Source Info")));
TextWriter.WriteLine($"Application type: {StringList(metaData.AppTypes ?? new List<string>())}");
TextWriter.WriteLine($"Package types: {StringList(metaData.PackageTypes ?? new List<string>())}");
TextWriter.WriteLine($"File extensions: {StringList(metaData.FileExtensions ?? new List<string>())}");
TextWriter.WriteLine(string.Format(MakeHeading("Detetected Targets")));
TextWriter.WriteLine($"Output types: {StringList(metaData.Outputs ?? new List<string>())}");
TextWriter.WriteLine($"OS Targets: {StringList(metaData.OSTargets ?? new List<string>())}");
TextWriter.WriteLine($"CPU Targets: {StringList(metaData.CPUTargets ?? new List<string>())}");
TextWriter.WriteLine($"Cloud targets: {StringList(metaData.CloudTargets ?? new List<string>())}");
TextWriter.WriteLine(string.Format(MakeHeading("Stats")));
TextWriter.WriteLine($"Files analyzed: {metaData.FilesAnalyzed}");
TextWriter.WriteLine($"Files skipped: {metaData.FilesSkipped}");
TextWriter.WriteLine($"Total files: {metaData.TotalFiles}");
TextWriter.WriteLine($"Total matches: {metaData.TotalMatchesCount} in {metaData.FilesAffected} file(s)");
TextWriter.WriteLine($"Unique matches: {metaData.UniqueMatchesCount}");
return build.ToString();
}
TextWriter.WriteLine(MakeHeading("UniqueTags"));
foreach (var tag in metaData.UniqueTags) TextWriter.WriteLine(tag);
/// <summary>
/// even out delineator for headings
/// </summary>
/// <param name="header"></param>
/// <returns></returns>
private string MakeHeading(string header)
{
StringBuilder build = new();
build.Append(string.Format("[{0}]", header));
for (int i = header.Length; i < COLUMN_MAX; i++)
{
build.Append("-");
}
TextWriter.WriteLine(MakeHeading("Select Counters"));
foreach (var tagCounter in metaData.TagCounters ?? new List<MetricTagCounter>())
TextWriter.WriteLine($"Tagname: {tagCounter.Tag}, Count: {tagCounter.Count}");
}
return build.ToString();
}
public void WriteMatch(MatchRecord match)
{
if (TextWriter is null) throw new ArgumentNullException(nameof(TextWriter));
var output = _formatString.Replace("%F", match.FileName);
output = output.Replace("%l", match.LanguageInfo.Name);
output = output.Replace("%t", match.LanguageInfo.Type.ToString());
output = output.Replace("%L", match.StartLocationLine.ToString());
output = output.Replace("%C", match.StartLocationColumn.ToString());
output = output.Replace("%l", match.EndLocationLine.ToString());
output = output.Replace("%c", match.EndLocationColumn.ToString());
output = output.Replace("%R", match.RuleId);
output = output.Replace("%N", match.RuleName);
output = output.Replace("%S", match.Severity.ToString());
output = output.Replace("%X", match.Confidence.ToString());
output = output.Replace("%D", match.RuleDescription);
output = output.Replace("%m", match.Sample);
output = output.Replace("%T", string.Join(',', match.Tags ?? Array.Empty<string>()));
public void WriteAppMeta(MetaData metaData)
{
if (TextWriter is null)
{
throw new ArgumentNullException(nameof(TextWriter));
}
//write predefined characteristics
TextWriter.WriteLine(string.Format(MakeHeading("Project Info")));
TextWriter.WriteLine($"Name: {metaData.ApplicationName + " " + metaData.SourceVersion}");
TextWriter.WriteLine($"Description: {metaData.Description}");
TextWriter.WriteLine($"Source path: {metaData.SourcePath}");
TextWriter.WriteLine($"Authors: {metaData.Authors}");
TextWriter.WriteLine($"Last Updated: {metaData.LastUpdated}");
TextWriter.WriteLine(
$"Languages: {(metaData.Languages is not null ? StringList(metaData.Languages) : string.Empty)}");
TextWriter.WriteLine(string.Format(MakeHeading("Scan Settings")));
TextWriter.WriteLine($"Date scanned: {metaData.DateScanned}");
TextWriter.WriteLine(string.Format(MakeHeading("Source Info")));
TextWriter.WriteLine($"Application type: {StringList(metaData.AppTypes ?? new List<string>())}");
TextWriter.WriteLine($"Package types: {StringList(metaData.PackageTypes ?? new List<string>())}");
TextWriter.WriteLine($"File extensions: {StringList(metaData.FileExtensions ?? new List<string>())}");
TextWriter.WriteLine(string.Format(MakeHeading("Detetected Targets")));
TextWriter.WriteLine($"Output types: {StringList(metaData.Outputs ?? new List<string>())}");
TextWriter.WriteLine($"OS Targets: {StringList(metaData.OSTargets ?? new List<string>())}");
TextWriter.WriteLine($"CPU Targets: {StringList(metaData.CPUTargets ?? new List<string>())}");
TextWriter.WriteLine($"Cloud targets: {StringList(metaData.CloudTargets ?? new List<string>())}");
TextWriter.WriteLine(string.Format(MakeHeading("Stats")));
TextWriter.WriteLine($"Files analyzed: {metaData.FilesAnalyzed}");
TextWriter.WriteLine($"Files skipped: {metaData.FilesSkipped}");
TextWriter.WriteLine($"Total files: {metaData.TotalFiles}");
TextWriter.WriteLine($"Total matches: {metaData.TotalMatchesCount} in {metaData.FilesAffected} file(s)");
TextWriter.WriteLine($"Unique matches: {metaData.UniqueMatchesCount}");
TextWriter.WriteLine(output);
}
TextWriter.WriteLine(MakeHeading("UniqueTags"));
foreach (string tag in metaData.UniqueTags)
{
TextWriter.WriteLine(tag);
}
private void WriteDependencies(MetaData metaData)
{
if (TextWriter is null) throw new ArgumentNullException(nameof(TextWriter));
TextWriter.WriteLine(MakeHeading("Dependencies"));
TextWriter.WriteLine(MakeHeading("Select Counters"));
foreach (MetricTagCounter tagCounter in metaData.TagCounters ?? new List<MetricTagCounter>())
{
TextWriter.WriteLine($"Tagname: {tagCounter.Tag}, Count: {tagCounter.Count}");
}
}
public void WriteMatch(MatchRecord match)
{
if (TextWriter is null)
{
throw new ArgumentNullException(nameof(TextWriter));
}
string output = _formatString.Replace("%F", match.FileName);
output = output.Replace("%l", match.LanguageInfo.Name);
output = output.Replace("%t", match.LanguageInfo.Type.ToString());
output = output.Replace("%L", match.StartLocationLine.ToString());
output = output.Replace("%C", match.StartLocationColumn.ToString());
output = output.Replace("%l", match.EndLocationLine.ToString());
output = output.Replace("%c", match.EndLocationColumn.ToString());
output = output.Replace("%R", match.RuleId);
output = output.Replace("%N", match.RuleName);
output = output.Replace("%S", match.Severity.ToString());
output = output.Replace("%X", match.Confidence.ToString());
output = output.Replace("%D", match.RuleDescription);
output = output.Replace("%m", match.Sample);
output = output.Replace("%T", string.Join(',', match.Tags ?? System.Array.Empty<string>()));
TextWriter.WriteLine(output);
}
private void WriteDependencies(MetaData metaData)
{
if (TextWriter is null)
{
throw new ArgumentNullException(nameof(TextWriter));
}
TextWriter.WriteLine(MakeHeading("Dependencies"));
foreach (string s in metaData.UniqueDependencies ?? new List<string>())
{
TextWriter.WriteLine(s);
}
}
private readonly string _formatString;
private readonly ILogger<AnalyzeJsonWriter> _logger;
foreach (var s in metaData.UniqueDependencies ?? new List<string>()) TextWriter.WriteLine(s);
}
}

Просмотреть файл

@ -1,26 +1,27 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
{
using Microsoft.ApplicationInspector.Commands;
using System.IO;
using System.IO;
using Microsoft.ApplicationInspector.Commands;
/// <summary>
/// Common class for command specific writers
/// </summary>
public abstract class CommandResultsWriter
namespace Microsoft.ApplicationInspector.CLI;
/// <summary>
/// Common class for command specific writers
/// </summary>
public abstract class CommandResultsWriter
{
protected CommandResultsWriter(TextWriter writer)
{
protected CommandResultsWriter(TextWriter writer)
{
TextWriter = writer;
}
public TextWriter TextWriter { get; }
public abstract void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true);
public void FlushAndClose()
{
TextWriter?.Flush();
TextWriter?.Close();
}
TextWriter = writer;
}
public TextWriter TextWriter { get; }
public abstract void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true);
public void FlushAndClose()
{
TextWriter?.Flush();
TextWriter?.Close();
}
}

Просмотреть файл

@ -1,50 +1,40 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
using System;
using System.IO;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.CLI;
internal class ExportTagsTextWriter : CommandResultsWriter
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.IO;
private readonly ILogger<ExportTagsTextWriter> _logger;
internal class ExportTagsTextWriter : CommandResultsWriter
internal ExportTagsTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
private readonly ILogger<ExportTagsTextWriter> _logger;
_logger = loggerFactory?.CreateLogger<ExportTagsTextWriter>() ?? NullLogger<ExportTagsTextWriter>.Instance;
}
internal ExportTagsTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
if (TextWriter is null) throw new ArgumentNullException(nameof(TextWriter));
var exportTagsResult = (ExportTagsResult)result;
if (exportTagsResult.TagsList.Count > 0)
{
_logger = loggerFactory?.CreateLogger<ExportTagsTextWriter>() ?? NullLogger<ExportTagsTextWriter>.Instance;
TextWriter.WriteLine("Results");
foreach (var tag in exportTagsResult.TagsList) TextWriter.WriteLine(tag);
}
else
{
TextWriter.WriteLine("No tags found");
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
if (TextWriter is null)
{
throw new ArgumentNullException(nameof(TextWriter));
}
ExportTagsResult exportTagsResult = (ExportTagsResult)result;
if (exportTagsResult.TagsList.Count > 0)
{
TextWriter.WriteLine("Results");
foreach (string tag in exportTagsResult.TagsList)
{
TextWriter.WriteLine(tag);
}
}
else
{
TextWriter.WriteLine("No tags found");
}
if (autoClose)
{
FlushAndClose();
}
}
if (autoClose) FlushAndClose();
}
}

Просмотреть файл

@ -1,51 +1,44 @@
namespace Microsoft.ApplicationInspector.CLI.Writers
using System;
using System.IO;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.CLI.Writers;
internal class JsonWriter : CommandResultsWriter
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.IO;
private readonly ILogger<JsonWriter> _logger;
internal class JsonWriter : CommandResultsWriter
internal JsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
private readonly ILogger<JsonWriter> _logger;
_logger = loggerFactory?.CreateLogger<JsonWriter>() ?? NullLogger<JsonWriter>.Instance;
}
internal JsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
JsonSerializer jsonSerializer = new();
jsonSerializer.Formatting = Formatting.Indented;
jsonSerializer.NullValueHandling = NullValueHandling.Ignore;
jsonSerializer.DefaultValueHandling = DefaultValueHandling.Ignore;
if (TextWriter is null) throw new ArgumentNullException(nameof(TextWriter));
switch (result)
{
_logger = loggerFactory?.CreateLogger<JsonWriter>() ?? NullLogger<JsonWriter>.Instance;
case TagDiffResult:
case ExportTagsResult:
case VerifyRulesResult:
jsonSerializer.Serialize(TextWriter, result);
break;
case PackRulesResult prr:
jsonSerializer.Serialize(TextWriter, prr.Rules);
break;
default:
throw new Exception("Unexpected object type for json writer");
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
JsonSerializer jsonSerializer = new();
jsonSerializer.Formatting = Formatting.Indented;
jsonSerializer.NullValueHandling = NullValueHandling.Ignore;
jsonSerializer.DefaultValueHandling = DefaultValueHandling.Ignore;
if (TextWriter is null)
{
throw new ArgumentNullException(nameof(TextWriter));
}
switch (result)
{
case TagDiffResult:
case ExportTagsResult:
case VerifyRulesResult:
jsonSerializer.Serialize(TextWriter, result);
break;
case PackRulesResult prr:
jsonSerializer.Serialize(TextWriter, prr.Rules);
break;
default:
throw new System.Exception("Unexpected object type for json writer");
}
if (autoClose)
{
FlushAndClose();
}
}
if (autoClose) FlushAndClose();
}
}

Просмотреть файл

@ -1,52 +1,43 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
using System.IO;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.CLI;
public class TagDiffTextWriter : CommandResultsWriter
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.IO;
private readonly ILogger<TagDiffTextWriter> _logger;
public class TagDiffTextWriter : CommandResultsWriter
public TagDiffTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
private readonly ILogger<TagDiffTextWriter> _logger;
_logger = loggerFactory?.CreateLogger<TagDiffTextWriter>() ?? NullLogger<TagDiffTextWriter>.Instance;
}
public TagDiffTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
var tagDiffResult = (TagDiffResult)result;
var cLITagDiffCmdOptions = (CLITagDiffCmdOptions)commandOptions;
TextWriter.WriteLine(MsgHelp.FormatString(MsgHelp.ID.TAGTEST_RESULTS_TEST_TYPE, cLITagDiffCmdOptions.TestType));
if (tagDiffResult.ResultCode == TagDiffResult.ExitCode.TestFailed)
TextWriter.WriteLine(MsgHelp.GetString(MsgHelp.ID.TAGTEST_RESULTS_FAIL));
else
TextWriter.WriteLine(MsgHelp.GetString(MsgHelp.ID.TAGTEST_RESULTS_SUCCESS));
//Results list
if (tagDiffResult.TagDiffList.Count > 0)
{
_logger = loggerFactory?.CreateLogger<TagDiffTextWriter>() ?? NullLogger<TagDiffTextWriter>.Instance;
TextWriter.WriteLine("Differences");
foreach (var tagDiff in tagDiffResult.TagDiffList)
TextWriter.WriteLine("Tag: {0}, Only found in file: {1}", tagDiff.Tag, tagDiff.Source);
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
TagDiffResult tagDiffResult = (TagDiffResult)result;
CLITagDiffCmdOptions cLITagDiffCmdOptions = (CLITagDiffCmdOptions)commandOptions;
TextWriter.WriteLine(MsgHelp.FormatString(MsgHelp.ID.TAGTEST_RESULTS_TEST_TYPE, cLITagDiffCmdOptions.TestType));
if (tagDiffResult.ResultCode == TagDiffResult.ExitCode.TestFailed)
{
TextWriter.WriteLine(MsgHelp.GetString(MsgHelp.ID.TAGTEST_RESULTS_FAIL));
}
else
{
TextWriter.WriteLine(MsgHelp.GetString(MsgHelp.ID.TAGTEST_RESULTS_SUCCESS));
}
//Results list
if (tagDiffResult.TagDiffList.Count > 0)
{
TextWriter.WriteLine("Differences");
foreach (TagDiff tagDiff in tagDiffResult.TagDiffList)
{
TextWriter.WriteLine(string.Format("Tag: {0}, Only found in file: {1}", tagDiff.Tag, tagDiff.Source));
}
}
if (autoClose)
{
FlushAndClose();
}
}
if (autoClose) FlushAndClose();
}
}

Просмотреть файл

@ -1,55 +1,42 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
using Microsoft.ApplicationInspector.RulesEngine;
using System.IO;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.CLI
namespace Microsoft.ApplicationInspector.CLI;
internal class VerifyRulesTextWriter : CommandResultsWriter
{
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.IO;
private readonly ILogger<VerifyRulesTextWriter> _logger;
internal class VerifyRulesTextWriter : CommandResultsWriter
public VerifyRulesTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
private readonly ILogger<VerifyRulesTextWriter> _logger;
_logger = loggerFactory?.CreateLogger<VerifyRulesTextWriter>() ?? NullLogger<VerifyRulesTextWriter>.Instance;
}
public VerifyRulesTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
var verifyRulesResult = (VerifyRulesResult)result;
if (string.IsNullOrEmpty(commandOptions.OutputFilePath)) TextWriter.WriteLine("Results");
if (verifyRulesResult.ResultCode != VerifyRulesResult.ExitCode.Verified)
TextWriter.WriteLine(MsgHelp.ID.TAGTEST_RESULTS_FAIL);
else
TextWriter.WriteLine(MsgHelp.ID.TAGTEST_RESULTS_SUCCESS);
if (verifyRulesResult.RuleStatusList.Count > 0)
{
_logger = loggerFactory?.CreateLogger<VerifyRulesTextWriter>() ?? NullLogger<VerifyRulesTextWriter>.Instance;
TextWriter.WriteLine("Rule status");
foreach (var ruleStatus in verifyRulesResult.RuleStatusList)
TextWriter.WriteLine("Ruleid: {0}, Rulename: {1}, Status: {2}", ruleStatus.RulesId,
ruleStatus.RulesName, ruleStatus.Verified);
}
public override void WriteResults(Result result, CLICommandOptions commandOptions, bool autoClose = true)
{
VerifyRulesResult verifyRulesResult = (VerifyRulesResult)result;
if (string.IsNullOrEmpty(commandOptions.OutputFilePath))
{
TextWriter.WriteLine("Results");
}
if (verifyRulesResult.ResultCode != VerifyRulesResult.ExitCode.Verified)
{
TextWriter.WriteLine(MsgHelp.ID.TAGTEST_RESULTS_FAIL);
}
else
{
TextWriter.WriteLine(MsgHelp.ID.TAGTEST_RESULTS_SUCCESS);
}
if (verifyRulesResult.RuleStatusList.Count > 0)
{
TextWriter.WriteLine("Rule status");
foreach (RuleStatus ruleStatus in verifyRulesResult.RuleStatusList)
{
TextWriter.WriteLine("Ruleid: {0}, Rulename: {1}, Status: {2}", ruleStatus.RulesId, ruleStatus.RulesName, ruleStatus.Verified);
}
}
if (autoClose)
{
FlushAndClose();
}
}
if (autoClose) FlushAndClose();
}
}

Просмотреть файл

@ -1,135 +1,130 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.CLI
using System;
using System.IO;
using Microsoft.ApplicationInspector.CLI.Writers;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.CLI;
public class WriterFactory
{
using Microsoft.ApplicationInspector.CLI.Writers;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.IO;
private readonly ILogger<WriterFactory> _logger;
private readonly ILoggerFactory? _loggerFactory;
public class WriterFactory
public WriterFactory(ILoggerFactory? loggerFactory = null)
{
private readonly ILoggerFactory? _loggerFactory;
private readonly ILogger<WriterFactory> _logger;
_loggerFactory = loggerFactory;
_logger = _loggerFactory?.CreateLogger<WriterFactory>() ?? NullLogger<WriterFactory>.Instance;
}
public WriterFactory(ILoggerFactory? loggerFactory = null)
/// <summary>
/// Responsible for returning the correct cmd and format writer for output of cmd results. An an output
/// file will be opened as a stream if provided otherwise the console.out stream is used
/// A downcast is expected as the input param containing the common output format and filepath for simplifying
/// the allocation to a single method and serves as a type selector but is also recast for command specific
/// options in the writer as needed
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
public CommandResultsWriter GetWriter(CLICommandOptions options)
{
return options switch
{
_loggerFactory = loggerFactory;
_logger = _loggerFactory?.CreateLogger<WriterFactory>() ?? NullLogger<WriterFactory>.Instance;
}
/// <summary>
/// Responsible for returning the correct cmd and format writer for output of cmd results. An an output
/// file will be opened as a stream if provided otherwise the console.out stream is used
/// A downcast is expected as the input param containing the common output format and filepath for simplifying
/// the allocation to a single method and serves as a type selector but is also recast for command specific
/// options in the writer as needed
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
public CommandResultsWriter GetWriter(CLICommandOptions options)
CLIAnalyzeCmdOptions cliAnalyzeCmdOptions => GetAnalyzeWriter(cliAnalyzeCmdOptions),
CLITagDiffCmdOptions cliTagDiffCmdOptions => GetTagDiffWriter(cliTagDiffCmdOptions),
CLIExportTagsCmdOptions cliExportTagsCmdOptions => GetExportTagsWriter(cliExportTagsCmdOptions),
CLIVerifyRulesCmdOptions cliVerifyRulesCmdOptions => GetVerifyRulesWriter(cliVerifyRulesCmdOptions),
CLIPackRulesCmdOptions cliPackRulesCmdOptions => GetPackRulesWriter(cliPackRulesCmdOptions),
_ => throw new OpException($"Unrecognized object type {options.GetType().Name} in writer request")
};
}
/// <summary>
/// Only AnalyzeResultsWriter supports an html option
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
private CommandResultsWriter GetAnalyzeWriter(CLIAnalyzeCmdOptions options)
{
var textWriter = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
return options switch
"json" => new AnalyzeJsonWriter(textWriter, _loggerFactory),
"text" => new AnalyzeTextWriter(textWriter, options.TextOutputFormat, _loggerFactory),
"html" => new AnalyzeHtmlWriter(textWriter, _loggerFactory),
"sarif" => new AnalyzeSarifWriter(textWriter, _loggerFactory),
_ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f"))
};
}
public CommandResultsWriter GetExportTagsWriter(CLIExportTagsCmdOptions options)
{
var writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
"text" => new ExportTagsTextWriter(writer, _loggerFactory),
_ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f"))
};
}
private CommandResultsWriter GetTagDiffWriter(CLITagDiffCmdOptions options)
{
var writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
"text" => new TagDiffTextWriter(writer, _loggerFactory),
_ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f"))
};
}
private CommandResultsWriter GetVerifyRulesWriter(CLIVerifyRulesCmdOptions options)
{
var writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
"text" => new VerifyRulesTextWriter(writer, _loggerFactory),
_ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f"))
};
}
private CommandResultsWriter GetPackRulesWriter(CLIPackRulesCmdOptions options)
{
var writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
_ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f"))
};
}
/// <summary>
/// Create a TextWriter for the given path or console.
/// </summary>
/// <param name="outputFileName">The path to create, if null or empty will use Console.Out.</param>
/// <returns></returns>
private TextWriter GetTextWriter(string? outputFileName)
{
TextWriter textWriter;
if (string.IsNullOrEmpty(outputFileName))
textWriter = Console.Out;
else
try
{
CLIAnalyzeCmdOptions cliAnalyzeCmdOptions => GetAnalyzeWriter(cliAnalyzeCmdOptions),
CLITagDiffCmdOptions cliTagDiffCmdOptions => GetTagDiffWriter(cliTagDiffCmdOptions),
CLIExportTagsCmdOptions cliExportTagsCmdOptions => GetExportTagsWriter(cliExportTagsCmdOptions),
CLIVerifyRulesCmdOptions cliVerifyRulesCmdOptions => GetVerifyRulesWriter(cliVerifyRulesCmdOptions),
CLIPackRulesCmdOptions cliPackRulesCmdOptions => GetPackRulesWriter(cliPackRulesCmdOptions),
_ => throw new OpException($"Unrecognized object type {options.GetType().Name} in writer request")
};
}
/// <summary>
/// Only AnalyzeResultsWriter supports an html option
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
private CommandResultsWriter GetAnalyzeWriter(CLIAnalyzeCmdOptions options)
{
TextWriter textWriter = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new AnalyzeJsonWriter(textWriter, _loggerFactory),
"text" => new AnalyzeTextWriter(textWriter, options.TextOutputFormat, _loggerFactory),
"html" => new AnalyzeHtmlWriter(textWriter, _loggerFactory),
"sarif" => new AnalyzeSarifWriter(textWriter, _loggerFactory),
_ => throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f"))
};
}
public CommandResultsWriter GetExportTagsWriter(CLIExportTagsCmdOptions options)
{
TextWriter writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
"text" => new ExportTagsTextWriter(writer, _loggerFactory),
_ => throw new OpException((MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f")))
};
}
private CommandResultsWriter GetTagDiffWriter(CLITagDiffCmdOptions options)
{
TextWriter writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
"text" => new TagDiffTextWriter(writer, _loggerFactory),
_ => throw new OpException((MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f")))
};
}
private CommandResultsWriter GetVerifyRulesWriter(CLIVerifyRulesCmdOptions options)
{
TextWriter writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
"text" => new VerifyRulesTextWriter(writer, _loggerFactory),
_ => throw new OpException((MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f")))
};
}
private CommandResultsWriter GetPackRulesWriter(CLIPackRulesCmdOptions options)
{
TextWriter writer = GetTextWriter(options.OutputFilePath);
return options.OutputFileFormat.ToLower() switch
{
"json" => new JsonWriter(writer, _loggerFactory),
_ => throw new OpException((MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_ARG_VALUE, "-f")))
};
}
/// <summary>
/// Create a TextWriter for the given path or console.
/// </summary>
/// <param name="outputFileName">The path to create, if null or empty will use Console.Out.</param>
/// <returns></returns>
private TextWriter GetTextWriter(string? outputFileName)
{
TextWriter textWriter;
if (string.IsNullOrEmpty(outputFileName))
{
textWriter = Console.Out;
textWriter = File.CreateText(outputFileName);
}
else
catch (Exception)
{
try
{
textWriter = File.CreateText(outputFileName);
}
catch (Exception)
{
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_FILE_OR_DIR), outputFileName);
throw;
}
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_FILE_OR_DIR), outputFileName);
throw;
}
return textWriter;
}
return textWriter;
}
}

Просмотреть файл

@ -1,68 +1,80 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.css" />
<link rel="stylesheet" type="text/css" href="html/resources/css/appinspector.css" />
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.css" rel="stylesheet"/>
<link href="html/resources/css/appinspector.css" rel="stylesheet" type="text/css"/>
<title>{{ application_version }}</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-light navbar-lg">
<nav class="navbar navbar-expand-md navbar-dark bg-light navbar-lg">
<span class="navbar-brand">
<img src="" id="ms_logo" width="32" height="32" class="d-inline-block align-top" alt="">
<img alt="" class="d-inline-block align-top" height="32" id="ms_logo" src="" width="32">
Application Inspector
</span>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li><a class="nav-button" href="#" data-target="#page__report_overview">Overview</a></li>
<li><a class="nav-button" href="#" data-target="#page__report_summary">Summary</a></li>
<li><a class="nav-button" href="#" data-target="#page__report_profile">Key Features</a></li>
<li><a class="nav-button" href="https://github.com/Microsoft/ApplicationInspector/wiki" data-target="#page__report_profile" target="_blank" rel="noopener">About</a></li>
</ul>
<ul class="navbar-nav ml-auto" style="margin-right: 80px">
<li>
<a class="nav-button" href="https://github.com/Microsoft/ApplicationInspector" target="_blank" rel="noopener noreferrer">
<i class="fab fa-github" style="font-size: 1.8em; color: #888"></i>
</a>
</li>
</ul>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li><a class="nav-button" data-target="#page__report_overview" href="#">Overview</a></li>
<li><a class="nav-button" data-target="#page__report_summary" href="#">Summary</a></li>
<li><a class="nav-button" data-target="#page__report_profile" href="#">Key Features</a></li>
<li><a class="nav-button" data-target="#page__report_profile"
href="https://github.com/Microsoft/ApplicationInspector/wiki" rel="noopener" target="_blank">About</a></li>
</ul>
<ul class="navbar-nav ml-auto" style="margin-right: 80px">
<li>
<a class="nav-button" href="https://github.com/Microsoft/ApplicationInspector" rel="noopener noreferrer"
target="_blank">
<i class="fab fa-github" style="font-size: 1.8em; color: #888"></i>
</a>
</li>
</ul>
</div>
</nav>
<main class="container-fluid" role="main">
<div class="row">
<div class="col col-lg-1"></div>
<div class="col" id="page__report_overview">
{% include "report_overview" %}
</div>
</nav>
<main role="main" class="container-fluid">
<div class="row">
<div class="col col-lg-1"></div>
<div class="col" id="page__report_overview">
{% include "report_overview" %}
</div>
<div class="col d-none" id="page__report_summary">
{% include "report_summary" %}
</div>
<div class="col d-none" id="page__report_profile">
{% include "report_profile" %}
</div>
<div class="col col-lg-2"></div>
<div class="col d-none" id="page__report_summary">
{% include "report_summary" %}
</div>
</main>
<div class="col d-none" id="page__report_profile">
{% include "report_profile" %}
</div>
<div class="col col-lg-2"></div>
</div>
</main>
{% include "file_listing" %}
{% include "file_listing" %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js" integrity="sha512-GZ1RIgZaSc8rnco/8CXfRdCpDxRCphenIiZ2ztLy3XQfCbQUSCuk8IudvNHxkRA3oUg6q0qejgN/qqyG1duv5Q==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha512-M5KW3ztuIICmVIhjSqXe01oV2bpe248gOxqmlcYrEzAvws7Pw3z6BK0iGbrwvdrUQUhi3eXgtxp5I8PDo9YfjQ==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.min.js" integrity="sha512-+IpCthlNahOuERYUSnKFjzjdKXIbJ/7Dd6xvUp+7bEw0Jp2dg6tluyxLs+zq9BMzZgrLv8886T4cBSqnKiVgUw==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js" integrity="sha512-FHsFVKQ/T1KWJDGSbrUhTJyS1ph3eRrxI228ND0EGaEp6v4a/vGwPWd3Dtd/+9cI7ccofZvl/wulICEurHN1pg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.5.3/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ext-beautify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ext-modelist.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ext-settings_menu.js"></script>
<script crossorigin="anonymous"
integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script crossorigin="anonymous"
integrity="sha512-GZ1RIgZaSc8rnco/8CXfRdCpDxRCphenIiZ2ztLy3XQfCbQUSCuk8IudvNHxkRA3oUg6q0qejgN/qqyG1duv5Q=="
src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></script>
<script crossorigin="anonymous"
integrity="sha512-M5KW3ztuIICmVIhjSqXe01oV2bpe248gOxqmlcYrEzAvws7Pw3z6BK0iGbrwvdrUQUhi3eXgtxp5I8PDo9YfjQ=="
src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script crossorigin="anonymous"
integrity="sha512-+IpCthlNahOuERYUSnKFjzjdKXIbJ/7Dd6xvUp+7bEw0Jp2dg6tluyxLs+zq9BMzZgrLv8886T4cBSqnKiVgUw=="
src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.min.js"></script>
<script crossorigin="anonymous"
integrity="sha512-FHsFVKQ/T1KWJDGSbrUhTJyS1ph3eRrxI228ND0EGaEp6v4a/vGwPWd3Dtd/+9cI7ccofZvl/wulICEurHN1pg=="
src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.5.3/umd/popper.min.js" integrity="sha512-53CQcu9ciJDlqhK7UD8dZZ+TF2PFGZrOngEYM/8qucuQba+a+BXOIRsp9PoMNJI3ZeLMVNIxIfZLbG/CdHI5PA==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ext-beautify.js" integrity="sha512-cbnV6WqDWdPXhI/SfbylU8ZOgxe6X7u1cAChSzCEgcixCoBlg0M7pZQgUmFFaStd1Y+pimB65mL9gUE0R2Xpug==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ext-modelist.js" integrity="sha512-Cp7JolVvryVA9mLSbTnQbpmfpCZg4VQ8BgKDgRcyNu3yWMitVcxzFj8/fcjxDrNvMjAPCU9Noygzc6eK9rP4Mg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ext-settings_menu.js" integrity="sha512-soczE7f5nUAF4LBvJ+N+h7mDqFWJT0zyhH+F1CiQns+r1EVS5Ze0qPgRm+2rJpuXggx07DMDCGdoaASwEAScrw==" crossorigin="anonymous"></script>
<script type="text/javascript">
let data = {{ json }};
</script>
<script type="text/javascript">
let data = {{ json }};
</script>
</body>
</html>

Просмотреть файл

@ -1,6 +1,6 @@
body {
background-color: #fcfefd !important;
font-family: 'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',sans-serif;
font-family: 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
}
a:hover {
@ -25,22 +25,22 @@ a:hover {
padding-bottom: 5px;
}
.navbar-lg .navbar-brand {
margin-left: 80px;
font-size: 24px;
font-weight: bold;
color: #000;
}
.navbar-lg .navbar-brand {
margin-left: 80px;
font-size: 24px;
font-weight: bold;
color: #000;
}
.navbar-lg .navbar-nav > li {
margin-left: 15px;
margin-right: 15px;
font-size: 0.90em;
}
.navbar-lg .navbar-nav > li {
margin-left: 15px;
margin-right: 15px;
font-size: 0.90em;
}
.navbar-lg .navbar-nav > li > a {
color: #000;
}
.navbar-lg .navbar-nav > li > a {
color: #000;
}
#file_listing_modal div.modal-dialog {
max-width: 70%;
@ -60,19 +60,19 @@ div.section {
padding: 0.10rem 0.55rem 0.10rem 0.55rem;
}
#page__report_overview table td:first-child {
text-align: center;
color: #888;
}
#page__report_overview table td:first-child {
text-align: center;
color: #888;
}
#page__report_overview table td:nth-child(2) {
text-align: center;
font-weight: bold;
}
#page__report_overview table td:nth-child(2) {
text-align: center;
font-weight: bold;
}
#page__report_overview table td:nth-child(3) {
color: #888;
}
#page__report_overview table td:nth-child(3) {
color: #888;
}
/* Report Summary */
.c3-chart-arcs-title {

Просмотреть файл

@ -46,8 +46,7 @@
// Decode the content (HTML encoded) for Ace to display
// Disabled, needs better testing, since it's prone to XSS if content contains JS.
// Maybe there is a better way of doing this.
if (false)
{
if (false) {
const htmlEntityDecoder = (content) => {
const textArea = document.createElement('textarea');
textArea.innerHTML = content;
@ -92,7 +91,9 @@ class TemplateInsertion {
donut: {
title: "Analyzed Files",
label: {
format: function(value) { return value; }
format: function (value) {
return value;
}
}
}
});
@ -112,7 +113,9 @@ class TemplateInsertion {
donut: {
title: "Results",
label: {
format: function(value) { return value; }
format: function (value) {
return value;
}
}
}
});
@ -129,7 +132,9 @@ class TemplateInsertion {
donut: {
title: "Source Types",
label: {
format: function(value) { return value; }
format: function (value) {
return value;
}
}
}
});
@ -149,8 +154,10 @@ class TemplateInsertion {
const _a = a.toLowerCase();
const _b = b.toLowerCase();
const map = { 'low': 1, 'medium': 2, 'high': 4 };
if (map[_a] > map[_b]) { return a; }
const map = {'low': 1, 'medium': 2, 'high': 4};
if (map[_a] > map[_b]) {
return a;
}
return b;
}
@ -228,14 +235,14 @@ class TemplateInsertion {
// We're goint to go through all results (this.md) and if it contains
// a tag that matches what we're looking for, we'll keep that icon visible.
search_loop:
for (let match of this.md) {
for (let tag of match.tags) {
if (targetRegex.exec(tag)) {
foundTag = true; // We have at least one match for this icon
break search_loop;
for (let match of this.md) {
for (let tag of match.tags) {
if (targetRegex.exec(tag)) {
foundTag = true; // We have at least one match for this icon
break search_loop;
}
}
}
}
if (!foundTag) {
$(elt).addClass('disabled');
}
@ -261,8 +268,7 @@ class TemplateInsertion {
//Toggles display blocks and image; NOTE jquery selectors won't work due to spaces in some group
//names even using escape() or '[id=value] methods so html DOM methods used and work
$('.toggle_image').click(function ()
{
$('.toggle_image').click(function () {
var btnToggleBlock = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAA2CAYAAACY0PQ8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAHYYAAB2GAV2iE4EAAACgSURBVGhD7dmxDcIwEEBRhyWYjumYjinCQ0qProIo/zV2aX35GnvbWRd3O9ZLKwJFoAgUgSJQBIpAESgCRaAIFIEiUASKQBEoAkWgCBSBIjB+ct+er2P33/bH/dh9N7oJZwnwMTlr40ARGEWYzNmvTc7aXySNA0WgCBSBIlAEikARKAJFoAgUgSJQBIpAESgCRaAIFIEiUASKQBEowlrrDWP+FV5LruwQAAAAAElFTkSuQmCC";
var btnToggleNone = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAA2CAYAAACY0PQ8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAADBSURBVGhD7dqxCYBADEDRnEs4ndM5nVPoP7hCxCbNBfG/Ru3kYxCD7UT83DKOv2YEGAFGgBFQ+nZo+zHOIs5tHWfzlT0J9wDd83omxwFGgBFgBBgBRoARYAQYAUaAEWAEGAFGgBGQXqpUfvdnZJY0qSfhKwG6zL06DjACUhEql6FZmXst2za/zWxVZMcBRoARYAQYAUaAEWAEGAFGgBFgBBgBRoARYAQYAWURnlukytWdv/rDcYARYAQYAUaAESLiAh45KVR0Pg9xAAAAAElFTkSuQmCC";
@ -302,10 +308,9 @@ class TemplateInsertion {
$tbody.empty();
// Now we iterate through all of the rules that relate to this icon
for (let [rule] of Object.entries(identifiedRules))
{
for (let [rule] of Object.entries(identifiedRules)) {
let $tr = $('<tr>');
$tr.on('click', 'td', { 'obj': this }, this.show_file_listing);
$tr.on('click', 'td', {'obj': this}, this.show_file_listing);
let $td0 = $('<td>');
let $td0a = $('<a>');
$td0a.attr('href', '#');

Просмотреть файл

@ -1,57 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.Common</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.Common</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.Common</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<PackageId>Microsoft.CST.ApplicationInspector.Common</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.Common</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.Common</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1"/>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="..\LICENSE.txt" Pack="true" PackagePath="" />
<None Include="..\icon-128.png" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig"/>
<None Include="..\LICENSE.txt" Pack="true" PackagePath=""/>
<None Include="..\icon-128.png" Pack="true" PackagePath=""/>
</ItemGroup>
</Project>

Просмотреть файл

@ -1,110 +1,109 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Common
using System;
using Microsoft.ApplicationInspector.Common.Properties;
namespace Microsoft.ApplicationInspector.Common;
public static class MsgHelp
{
using Microsoft.ApplicationInspector.Common.Properties;
using System;
public static class MsgHelp
/// <summary>
/// Maps enum values to resource strings for ensuring values exists at compile time
/// </summary>
public enum ID
{
/// <summary>
/// Maps enum values to resource strings for ensuring values exists at compile time
/// </summary>
public enum ID
{
ANALYZE_COMPRESSED_FILETYPE,
ANALYZE_FILES_PROCESSED_PCNT,
ANALYZE_NOPATTERNS,
ANALYZE_PROCESSING_TIMED_OUT,
ANALYZE_NOSUPPORTED_FILETYPES,
ANALYZE_UNCOMPRESSED_FILETYPE,
ANALYZE_UNSUPPORTED_COMPR_TYPE,
ANALYZE_FILESIZE_SKIPPED,
ANALYZE_EXCLUDED_TYPE_SKIPPED,
ANALYZE_EXCLUDED_BINARY,
ANALYZE_LANGUAGE_NOTFOUND,
ANALYZE_COMPRESSED_FILESIZE_WARN,
ANALYZE_COMPRESSED_PROCESSING,
ANALYZE_COMPRESSED_ERROR,
ANALYZE_FILE_TYPE_OPEN,
ANALYZE_REPORTSIZE_WARN,
ANALYZE_NODUPLICATES_HTML_FORMAT,
ANALYZE_SIMPLETAGS_HTML_FORMAT,
ANALYZE_HTML_EXTENSION,
CMD_NO_OUTPUT,
CMD_PREPARING_REPORT,
CMD_COMPLETED,
CMD_CRITICAL_FILE_ERR,
CMD_INVALID_ARG_VALUE,
CMD_INVALID_FILE_OR_DIR,
CMD_NO_FILES_IN_SOURCE,
CMD_REPORT_DONE,
CMD_REQUIRED_ARG_MISSING,
CMD_RUNNING,
CMD_INVALID_RULE_PATH,
CMD_NORULES_SPECIFIED,
CMD_INVALID_LOG_PATH,
CMD_VIEW_OUTPUT_FILE,
CMD_REMINDER_CHECK_LOG,
TAGDIFF_NO_TAGS_FOUND,
TAGDIFF_RESULTS_DIFFER,
TAGDIFF_RESULTS_GAP,
TAGDIFF_RESULTS_TEST_TYPE,
TAGDIFF_SAME_FILE_ARG,
TAGDIFF_RESULTS_SUCCESSS,
TAGDIFF_RESULTS_FAIL,
TAGTEST_RESULTS_NONE,
TAGTEST_RESULTS_TAGS_FOUND,
TAGTEST_RESULTS_TAGS_MISSING,
TAGTEST_RESULTS_TEST_TYPE,
TAGTEST_RESULTS_SUCCESS,
TAGTEST_RESULTS_FAIL,
VERIFY_RULE_LOADFILE_FAILED,
VERIFY_RULES_RESULTS_FAIL,
VERIFY_RULES_NULLID_FAIL,
VERIFY_RULES_REGEX_FAIL,
VERIFY_RULES_LANGUAGE_FAIL,
VERIFY_RULES_CONDITION_FAIL,
VERIFY_RULES_DUPLICATEID_FAIL,
VERIFY_RULES_RESULTS_SUCCESS,
VERIFY_RULES_NO_CLI_DEFAULT,
RUNTIME_ERROR_NAMED,
RUNTIME_ERROR_UNNAMED,
RUNTIME_ERROR_PRELOG,
BROWSER_ENVIRONMENT_VAR,
BROWSER_START_FAIL,
BROWSER_START_SUCCESS,
PACK_MISSING_OUTPUT_ARG,
PACK_RULES_NO_CLI_DEFAULT,
PACK_RULES_NO_DEFAULT,
};
ANALYZE_COMPRESSED_FILETYPE,
ANALYZE_FILES_PROCESSED_PCNT,
ANALYZE_NOPATTERNS,
ANALYZE_PROCESSING_TIMED_OUT,
ANALYZE_NOSUPPORTED_FILETYPES,
ANALYZE_UNCOMPRESSED_FILETYPE,
ANALYZE_UNSUPPORTED_COMPR_TYPE,
ANALYZE_FILESIZE_SKIPPED,
ANALYZE_EXCLUDED_TYPE_SKIPPED,
ANALYZE_EXCLUDED_BINARY,
ANALYZE_LANGUAGE_NOTFOUND,
ANALYZE_COMPRESSED_FILESIZE_WARN,
ANALYZE_COMPRESSED_PROCESSING,
ANALYZE_COMPRESSED_ERROR,
ANALYZE_FILE_TYPE_OPEN,
ANALYZE_REPORTSIZE_WARN,
ANALYZE_NODUPLICATES_HTML_FORMAT,
ANALYZE_SIMPLETAGS_HTML_FORMAT,
ANALYZE_HTML_EXTENSION,
CMD_NO_OUTPUT,
CMD_PREPARING_REPORT,
CMD_COMPLETED,
CMD_CRITICAL_FILE_ERR,
CMD_INVALID_ARG_VALUE,
CMD_INVALID_FILE_OR_DIR,
CMD_NO_FILES_IN_SOURCE,
CMD_REPORT_DONE,
CMD_REQUIRED_ARG_MISSING,
CMD_RUNNING,
CMD_INVALID_RULE_PATH,
CMD_NORULES_SPECIFIED,
CMD_INVALID_LOG_PATH,
CMD_VIEW_OUTPUT_FILE,
CMD_REMINDER_CHECK_LOG,
TAGDIFF_NO_TAGS_FOUND,
TAGDIFF_RESULTS_DIFFER,
TAGDIFF_RESULTS_GAP,
TAGDIFF_RESULTS_TEST_TYPE,
TAGDIFF_SAME_FILE_ARG,
TAGDIFF_RESULTS_SUCCESSS,
TAGDIFF_RESULTS_FAIL,
TAGTEST_RESULTS_NONE,
TAGTEST_RESULTS_TAGS_FOUND,
TAGTEST_RESULTS_TAGS_MISSING,
TAGTEST_RESULTS_TEST_TYPE,
TAGTEST_RESULTS_SUCCESS,
TAGTEST_RESULTS_FAIL,
VERIFY_RULE_LOADFILE_FAILED,
VERIFY_RULES_RESULTS_FAIL,
VERIFY_RULES_NULLID_FAIL,
VERIFY_RULES_REGEX_FAIL,
VERIFY_RULES_LANGUAGE_FAIL,
VERIFY_RULES_CONDITION_FAIL,
VERIFY_RULES_DUPLICATEID_FAIL,
VERIFY_RULES_RESULTS_SUCCESS,
VERIFY_RULES_NO_CLI_DEFAULT,
RUNTIME_ERROR_NAMED,
RUNTIME_ERROR_UNNAMED,
RUNTIME_ERROR_PRELOG,
BROWSER_ENVIRONMENT_VAR,
BROWSER_START_FAIL,
BROWSER_START_SUCCESS,
PACK_MISSING_OUTPUT_ARG,
PACK_RULES_NO_CLI_DEFAULT,
PACK_RULES_NO_DEFAULT
}
public static string GetString(MsgHelp.ID id)
public static string GetString(ID id)
{
string result;
try
{
string result;
try
{
result = Resources.ResourceManager.GetString(id.ToString()) ?? "";
}
catch (Exception e)
{
string error = string.Format("Unable to locate requested string resource {0}", id);
error += e.Message + "\n" + e.StackTrace;
throw new Exception(error);
}
return result ?? "";
result = Resources.ResourceManager.GetString(id.ToString()) ?? "";
}
catch (Exception e)
{
var error = string.Format("Unable to locate requested string resource {0}", id);
error += e.Message + "\n" + e.StackTrace;
throw new Exception(error);
}
public static string FormatString(MsgHelp.ID id, params object[]? parameters)
{
return string.Format(GetString(id), parameters ?? Array.Empty<object>());
}
return result ?? "";
}
public static string FormatString(MsgHelp.ID id, int value)
{
return string.Format(GetString(id), value);
}
public static string FormatString(ID id, params object[]? parameters)
{
return string.Format(GetString(id), parameters ?? Array.Empty<object>());
}
public static string FormatString(ID id, int value)
{
return string.Format(GetString(id), value);
}
}

Просмотреть файл

@ -1,29 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.ApplicationInspector.Common
namespace Microsoft.ApplicationInspector.Common;
/// <summary>
/// Used to distinguish exceptions which are expected to have been safely written to log and console for CLI use
/// to avoid duplication of error messages to better support both CLI and NuGet entry / exit points
/// </summary>
[ExcludeFromCodeCoverage]
public class OpException : Exception
{
using System;
/// <summary>
/// Used to distinguish exceptions which are expected to have been safely written to log and console for CLI use
/// to avoid duplication of error messages to better support both CLI and NuGet entry / exit points
/// </summary>
[ExcludeFromCodeCoverage]
public class OpException : Exception
public OpException(string? msg) : base(msg)
{
public OpException(string? msg) : base(msg)
{
}
}
public OpException() : base()
{
}
public OpException()
{
}
public OpException(string? message, Exception? innerException) : base(message, innerException)
{
}
public OpException(string? message, Exception? innerException) : base(message, innerException)
{
}
}

Просмотреть файл

@ -3,12 +3,12 @@
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishDir>bin\Release\netcoreapp3.0\publish\</PublishDir>
<SelfContained>false</SelfContained>
</PropertyGroup>
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishDir>bin\Release\netcoreapp3.0\publish\</PublishDir>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

1
AppInspector.Common/Properties/Resources.Designer.cs сгенерированный
Просмотреть файл

@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

Просмотреть файл

@ -1,316 +1,326 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root"
xmlns="">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ANALYZE_COMPRESSED_ERROR" xml:space="preserve">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="ANALYZE_COMPRESSED_ERROR" xml:space="preserve">
<value>Problem while decompressing {0}: Unzip and try running on uncompressed files</value>
</data>
<data name="ANALYZE_COMPRESSED_FILESIZE_WARN" xml:space="preserve">
<data name="ANALYZE_COMPRESSED_FILESIZE_WARN" xml:space="preserve">
<value>Decompressing may take longer for larger files</value>
</data>
<data name="ANALYZE_COMPRESSED_FILETYPE" xml:space="preserve">
<data name="ANALYZE_COMPRESSED_FILETYPE" xml:space="preserve">
<value>compressed</value>
</data>
<data name="ANALYZE_COMPRESSED_PROCESSING" xml:space="preserve">
<data name="ANALYZE_COMPRESSED_PROCESSING" xml:space="preserve">
<value>Decompressing files...</value>
</data>
<data name="ANALYZE_EXCLUDED_TYPE_SKIPPED" xml:space="preserve">
<data name="ANALYZE_EXCLUDED_TYPE_SKIPPED" xml:space="preserve">
<value>File skipped: Name or path in exclude list. {0}</value>
</data>
<data name="ANALYZE_EXCLUDED_BINARY" xml:space="preserve">
<data name="ANALYZE_EXCLUDED_BINARY" xml:space="preserve">
<value>File skipped: First 100 bytes appear to indicate this is a binary file. {0}</value>
</data>
<data name="ANALYZE_FILESIZE_SKIPPED" xml:space="preserve">
<data name="ANALYZE_FILESIZE_SKIPPED" xml:space="preserve">
<value>File skipped: File size is too large. {0}</value>
</data>
<data name="ANALYZE_FILES_PROCESSED_PCNT" xml:space="preserve">
<data name="ANALYZE_FILES_PROCESSED_PCNT" xml:space="preserve">
<value>{0}% source files processed</value>
</data>
<data name="ANALYZE_FILE_TYPE_OPEN" xml:space="preserve">
<data name="ANALYZE_FILE_TYPE_OPEN" xml:space="preserve">
<value>Unable to determine file type. File open error for {0}</value>
</data>
<data name="ANALYZE_HTML_EXTENSION" xml:space="preserve">
<data name="ANALYZE_HTML_EXTENSION" xml:space="preserve">
<value>The output file does not have an html or htm extension and may not open properly in your browser</value>
</data>
<data name="ANALYZE_LANGUAGE_NOTFOUND" xml:space="preserve">
<data name="ANALYZE_LANGUAGE_NOTFOUND" xml:space="preserve">
<value>File skipped: Language not found for file {0}</value>
</data>
<data name="ANALYZE_NODUPLICATES_HTML_FORMAT" xml:space="preserve">
<data name="ANALYZE_NODUPLICATES_HTML_FORMAT" xml:space="preserve">
<value>Allow duplicate matches argument not supported for html output format. Select a different output format (text/json) or remove the argument.</value>
</data>
<data name="ANALYZE_NOPATTERNS" xml:space="preserve">
<data name="ANALYZE_NOPATTERNS" xml:space="preserve">
<value>No pattern matches were detected for files in source path</value>
</data>
<data name="ANALYZE_NOSUPPORTED_FILETYPES" xml:space="preserve">
<data name="ANALYZE_NOSUPPORTED_FILETYPES" xml:space="preserve">
<value>No file types found in source path that are supported</value>
</data>
<data name="ANALYZE_REPORTSIZE_WARN" xml:space="preserve">
<data name="ANALYZE_REPORTSIZE_WARN" xml:space="preserve">
<value>The output.html file size is large and may render slowly. Consider running using alternate output format options as needed.</value>
</data>
<data name="ANALYZE_SIMPLETAGS_HTML_FORMAT" xml:space="preserve">
<data name="ANALYZE_SIMPLETAGS_HTML_FORMAT" xml:space="preserve">
<value>Simple tags only argument not supported for html output format. Select a different output format or remove the argument</value>
</data>
<data name="ANALYZE_UNCOMPRESSED_FILETYPE" xml:space="preserve">
<data name="ANALYZE_UNCOMPRESSED_FILETYPE" xml:space="preserve">
<value>uncompressed</value>
</data>
<data name="ANALYZE_UNSUPPORTED_COMPR_TYPE" xml:space="preserve">
<data name="ANALYZE_UNSUPPORTED_COMPR_TYPE" xml:space="preserve">
<value>unsupported</value>
</data>
<data name="BROWSER_ENVIRONMENT_VAR" xml:space="preserve">
<data name="BROWSER_ENVIRONMENT_VAR" xml:space="preserve">
<value>Unable to launch output.html automatically. Set the BROWSER environment variable to your desired browser and try again or launch your browser and navigate to the file to view the report file manually.</value>
</data>
<data name="BROWSER_START_FAIL" xml:space="preserve">
<data name="BROWSER_START_FAIL" xml:space="preserve">
<value>Unable to launch output.html in default browser. Launch your browser manually to view output.html report file</value>
</data>
<data name="BROWSER_START_SUCCESS" xml:space="preserve">
<data name="BROWSER_START_SUCCESS" xml:space="preserve">
<value>Opening default browser to output.html report</value>
</data>
<data name="CMD_COMPLETED" xml:space="preserve">
<data name="CMD_COMPLETED" xml:space="preserve">
<value>{0} command completed</value>
</data>
<data name="CMD_CRITICAL_FILE_ERR" xml:space="preserve">
<data name="CMD_CRITICAL_FILE_ERR" xml:space="preserve">
<value>Critical error processing file {0}</value>
</data>
<data name="CMD_INVALID_ARG_VALUE" xml:space="preserve">
<data name="CMD_INVALID_ARG_VALUE" xml:space="preserve">
<value>Invalid {0} argument value</value>
</data>
<data name="CMD_INVALID_FILE_OR_DIR" xml:space="preserve">
<data name="CMD_INVALID_FILE_OR_DIR" xml:space="preserve">
<value>Invalid file or directory {0}</value>
</data>
<data name="CMD_NO_FILES_IN_SOURCE" xml:space="preserve">
<data name="CMD_NO_FILES_IN_SOURCE" xml:space="preserve">
<value>Specified source location does not contain any files. {0}</value>
</data>
<data name="CMD_INVALID_LOG_PATH" xml:space="preserve">
<data name="CMD_INVALID_LOG_PATH" xml:space="preserve">
<value>The requested log file {0} could not be read.</value>
</data>
<data name="CMD_INVALID_RULE_PATH" xml:space="preserve">
<data name="CMD_INVALID_RULE_PATH" xml:space="preserve">
<value>Invalid rule path {0}</value>
</data>
<data name="CMD_NORULES_SPECIFIED" xml:space="preserve">
<data name="CMD_NORULES_SPECIFIED" xml:space="preserve">
<value>No rules specified. At least one valid rules path required</value>
</data>
<data name="CMD_NO_OUTPUT" xml:space="preserve">
<data name="CMD_NO_OUTPUT" xml:space="preserve">
<value>No output specified. Raise the console versosity or provide an output file argument.</value>
</data>
<data name="CMD_PREPARING_REPORT" xml:space="preserve">
<data name="CMD_PREPARING_REPORT" xml:space="preserve">
<value>Preparing report</value>
</data>
<data name="CMD_REMINDER_CHECK_LOG" xml:space="preserve">
<data name="CMD_REMINDER_CHECK_LOG" xml:space="preserve">
<value>Additional details may be found in log file at {0}</value>
</data>
<data name="CMD_REPORT_DONE" xml:space="preserve">
<data name="CMD_REPORT_DONE" xml:space="preserve">
<value>{0} report completed</value>
</data>
<data name="CMD_REQUIRED_ARG_MISSING" xml:space="preserve">
<data name="CMD_REQUIRED_ARG_MISSING" xml:space="preserve">
<value>Required {0} argument missing</value>
</data>
<data name="CMD_RUNNING" xml:space="preserve">
<data name="CMD_RUNNING" xml:space="preserve">
<value>{0} command running</value>
</data>
<data name="CMD_VIEW_OUTPUT_FILE" xml:space="preserve">
<data name="CMD_VIEW_OUTPUT_FILE" xml:space="preserve">
<value>See output file at {0}</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="comments" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\comments.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="languages" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\languages.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="PACK_MISSING_OUTPUT_ARG" xml:space="preserve">
<assembly alias="System.Windows.Forms"
name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<data name="comments" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\comments.json;System.Byte[], mscorlib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
</value>
</data>
<data name="languages" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\languages.json;System.Byte[], mscorlib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
</value>
</data>
<data name="PACK_MISSING_OUTPUT_ARG" xml:space="preserve">
<value>Output file not specified for custom rules pack command</value>
</data>
<data name="PACK_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<data name="PACK_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<value>Pack default rules request from outside CLI call not supported. Use custom rules option.</value>
</data>
<data name="PACK_RULES_NO_DEFAULT" xml:space="preserve">
<data name="PACK_RULES_NO_DEFAULT" xml:space="preserve">
<value>Unable to locate local default rules folder. This command is intended to be run as part of the source code build operation only. Use the custom rules option instead.</value>
</data>
<data name="RUNTIME_ERROR_NAMED" xml:space="preserve">
<data name="RUNTIME_ERROR_NAMED" xml:space="preserve">
<value>Additional details may be found in log file at {0}</value>
</data>
<data name="RUNTIME_ERROR_PRELOG" xml:space="preserve">
<data name="RUNTIME_ERROR_PRELOG" xml:space="preserve">
<value>No additional information or log available</value>
</data>
<data name="RUNTIME_ERROR_UNNAMED" xml:space="preserve">
<data name="RUNTIME_ERROR_UNNAMED" xml:space="preserve">
<value>Additional details may be found in log file at {0}</value>
</data>
<data name="TAGDIFF_NO_TAGS_FOUND" xml:space="preserve">
<data name="TAGDIFF_NO_TAGS_FOUND" xml:space="preserve">
<value>No tags found in one or both source paths</value>
</data>
<data name="TAGDIFF_RESULTS_DIFFER" xml:space="preserve">
<data name="TAGDIFF_RESULTS_DIFFER" xml:space="preserve">
<value>Files contain tag differences: </value>
</data>
<data name="TAGDIFF_RESULTS_FAIL" xml:space="preserve">
<data name="TAGDIFF_RESULTS_FAIL" xml:space="preserve">
<value>failed</value>
</data>
<data name="TAGDIFF_RESULTS_GAP" xml:space="preserve">
<data name="TAGDIFF_RESULTS_GAP" xml:space="preserve">
<value>Tags in {0} not detected in {1}:</value>
</data>
<data name="TAGDIFF_RESULTS_SUCCESS" xml:space="preserve">
<data name="TAGDIFF_RESULTS_SUCCESS" xml:space="preserve">
<value>succeeded</value>
</data>
<data name="TAGDIFF_RESULTS_TEST_TYPE" xml:space="preserve">
<data name="TAGDIFF_RESULTS_TEST_TYPE" xml:space="preserve">
<value>Test for all [{0}] in source</value>
</data>
<data name="TAGDIFF_SAME_FILE_ARG" xml:space="preserve">
<data name="TAGDIFF_SAME_FILE_ARG" xml:space="preserve">
<value>Same file passed in for both sources. Test terminated</value>
</data>
<data name="TAGTEST_RESULTS_FAIL" xml:space="preserve">
<data name="TAGTEST_RESULTS_FAIL" xml:space="preserve">
<value>failed</value>
</data>
<data name="TAGTEST_RESULTS_NONE" xml:space="preserve">
<data name="TAGTEST_RESULTS_NONE" xml:space="preserve">
<value>none</value>
</data>
<data name="TAGTEST_RESULTS_SUCCESS" xml:space="preserve">
<data name="TAGTEST_RESULTS_SUCCESS" xml:space="preserve">
<value>succeeded</value>
</data>
<data name="TAGTEST_RESULTS_TAGS_FOUND" xml:space="preserve">
<data name="TAGTEST_RESULTS_TAGS_FOUND" xml:space="preserve">
<value>Found {0} in source</value>
</data>
<data name="TAGTEST_RESULTS_TAGS_MISSING" xml:space="preserve">
<data name="TAGTEST_RESULTS_TAGS_MISSING" xml:space="preserve">
<value>Missing {0} in source</value>
</data>
<data name="TAGTEST_RESULTS_TEST_TYPE" xml:space="preserve">
<data name="TAGTEST_RESULTS_TEST_TYPE" xml:space="preserve">
<value>Tagtest for [{0}]: </value>
</data>
<data name="VERIFY_RULES_DUPLICATEID_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_DUPLICATEID_FAIL" xml:space="preserve">
<value>Rule {0} failed from dupicate rule id specified</value>
</data>
<data name="VERIFY_RULES_LANGUAGE_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_LANGUAGE_FAIL" xml:space="preserve">
<value>Rule {0} failed from unrecognized language {1} specified</value>
</data>
<data name="VERIFY_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<data name="VERIFY_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<value>Verify default rules request from outside CLI call not supported. Use custom rules option.</value>
</data>
<data name="VERIFY_RULES_NULLID_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_NULLID_FAIL" xml:space="preserve">
<value>Rule {0} failed from missing rule id</value>
</data>
<data name="VERIFY_RULES_REGEX_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_REGEX_FAIL" xml:space="preserve">
<value>Rule {0} failed from invalid regex '{1}' with {2}</value>
</data>
<data name="VERIFY_RULES_RESULTS_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_RESULTS_FAIL" xml:space="preserve">
<value>Verify rules failed. See log file for details</value>
</data>
<data name="VERIFY_RULES_RESULTS_SUCCESS" xml:space="preserve">
<data name="VERIFY_RULES_RESULTS_SUCCESS" xml:space="preserve">
<value>Verify rules succeeded</value>
</data>
<data name="VERIFY_RULE_LOADFILE_FAILED" xml:space="preserve">
<data name="VERIFY_RULE_LOADFILE_FAILED" xml:space="preserve">
<value>Rule parsing failed for file {0}</value>
</data>
</root>

Просмотреть файл

@ -1,76 +1,85 @@
namespace Microsoft.ApplicationInspector.Common
using System;
using System.IO;
using System.Reflection;
namespace Microsoft.ApplicationInspector.Common;
public static class Utils
{
using System.IO;
using System.Reflection;
public static class Utils
public enum AppPath
{
public enum ExitCode
{
Success = 0,
PartialFail = 1,
CriticalError = 2
}
private static string? _basePath;
public enum AppPath { basePath, defaultRulesSrc, defaultRulesPackedFile, defaultLog, tagGroupPref, tagCounterPref };
public static string GetVersionString()
{
return string.Format("Microsoft Application Inspector {0}", GetVersion());
}
public static string GetVersion()
{
return (Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false) as AssemblyInformationalVersionAttribute[])?[0].InformationalVersion ?? "Unknown";
}
public static bool CLIExecutionContext { get; set; }
public static string GetPath(AppPath pathType)
{
string result = "";
switch (pathType)
{
case AppPath.basePath:
result = GetBaseAppPath();
break;
case AppPath.defaultLog:
result = "appinspector.log.txt";
break;
case AppPath.defaultRulesSrc://Packrules source use
result = Path.GetFullPath(Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector", "rules", "default"));//used to ref project folder
break;
case AppPath.defaultRulesPackedFile://Packrules default output use
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector", "Resources", "defaultRulesPkd.json");//packed default file in project resources
break;
case AppPath.tagGroupPref://CLI use only
result = Path.Combine(GetBaseAppPath(), "preferences", "tagreportgroups.json");
break;
case AppPath.tagCounterPref://CLI use only
result = Path.Combine(GetBaseAppPath(), "preferences", "tagcounters.json");
break;
}
result = Path.GetFullPath(result);
return result;
}
private static string GetBaseAppPath()
{
if (!string.IsNullOrEmpty(_basePath))
{
return _basePath;
}
_basePath = Path.GetFullPath(System.AppContext.BaseDirectory);
return _basePath;
}
basePath,
defaultRulesSrc,
defaultRulesPackedFile,
defaultLog,
tagGroupPref,
tagCounterPref
}
}
public enum ExitCode
{
Success = 0,
PartialFail = 1,
CriticalError = 2
}
private static string? _basePath;
public static bool CLIExecutionContext { get; set; }
public static string GetVersionString()
{
return string.Format("Microsoft Application Inspector {0}", GetVersion());
}
public static string GetVersion()
{
return (Assembly.GetExecutingAssembly()
.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false) as
AssemblyInformationalVersionAttribute[])?[0].InformationalVersion ?? "Unknown";
}
public static string GetPath(AppPath pathType)
{
var result = "";
switch (pathType)
{
case AppPath.basePath:
result = GetBaseAppPath();
break;
case AppPath.defaultLog:
result = "appinspector.log.txt";
break;
case AppPath.defaultRulesSrc: //Packrules source use
result = Path.GetFullPath(Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector",
"rules", "default")); //used to ref project folder
break;
case AppPath.defaultRulesPackedFile: //Packrules default output use
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector", "Resources",
"defaultRulesPkd.json"); //packed default file in project resources
break;
case AppPath.tagGroupPref: //CLI use only
result = Path.Combine(GetBaseAppPath(), "preferences", "tagreportgroups.json");
break;
case AppPath.tagCounterPref: //CLI use only
result = Path.Combine(GetBaseAppPath(), "preferences", "tagcounters.json");
break;
}
result = Path.GetFullPath(result);
return result;
}
private static string GetBaseAppPath()
{
if (!string.IsNullOrEmpty(_basePath)) return _basePath;
_basePath = Path.GetFullPath(AppContext.BaseDirectory);
return _basePath;
}
}

Просмотреть файл

@ -1,45 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.Logging</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.Logging</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.Logging</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.Logging</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.Logging</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.Logging</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig"/>
<None Include="..\LICENSE.txt" Pack="true" PackagePath=""/>
<None Include="..\icon-128.png" Pack="true" PackagePath=""/>
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="..\LICENSE.txt" Pack="true" PackagePath="" />
<None Include="..\icon-128.png" Pack="true" PackagePath="" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,44 +1,42 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Logging
using CommandLine;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
namespace Microsoft.ApplicationInspector.Logging;
/// <summary>
/// base for common options for the logging in commands
/// </summary>
public record LogOptions
{
using CommandLine;
using Microsoft.Extensions.Logging;
using Serilog;
[Option('x', "console-verbosity", Required = false,
HelpText = "Console verbosity [Verbose|Debug|Information|Warning|Error|Fatal]",
Default = LogEventLevel.Information)]
public LogEventLevel ConsoleVerbosityLevel { get; set; } = LogEventLevel.Information;
/// <summary>
/// base for common options for the logging in commands
/// </summary>
public record LogOptions
[Option("disable-console", Required = false, HelpText = "Disable console output of logging messages.",
Default = false)]
public bool DisableConsoleOutput { get; set; } = false;
[Option('v', "log-file-level", Required = false,
HelpText = "Log file level [Verbose|Debug|Information|Warning|Error|Fatal]", Default = LogEventLevel.Error)]
public LogEventLevel LogFileLevel { get; set; } = LogEventLevel.Error;
[Option('l', "log-file-path", Required = false, HelpText = "Log file path. If not set, will not log to file.")]
public string? LogFilePath { get; set; }
public ILoggerFactory GetLoggerFactory()
{
[Option('x', "console-verbosity", Required = false, HelpText = "Console verbosity [Verbose|Debug|Information|Warning|Error|Fatal]", Default = Serilog.Events.LogEventLevel.Information)]
public Serilog.Events.LogEventLevel ConsoleVerbosityLevel { get; set; } = Serilog.Events.LogEventLevel.Information;
[Option("disable-console", Required = false, HelpText = "Disable console output of logging messages.", Default = false)]
public bool DisableConsoleOutput { get; set; } = false;
[Option('v', "log-file-level", Required = false, HelpText = "Log file level [Verbose|Debug|Information|Warning|Error|Fatal]", Default = Serilog.Events.LogEventLevel.Error)]
public Serilog.Events.LogEventLevel LogFileLevel { get; set; } = Serilog.Events.LogEventLevel.Error;
[Option('l', "log-file-path", Required = false, HelpText = $"Log file path. If not set, will not log to file.")]
public string? LogFilePath { get; set; }
public ILoggerFactory GetLoggerFactory()
{
var configuration = new LoggerConfiguration()
.MinimumLevel.Is(ConsoleVerbosityLevel < LogFileLevel ? ConsoleVerbosityLevel : LogFileLevel);
if (!string.IsNullOrEmpty(LogFilePath))
{
configuration = configuration.WriteTo.File(LogFilePath, LogFileLevel);
}
if (!DisableConsoleOutput)
{
configuration = configuration.WriteTo.Console(ConsoleVerbosityLevel);
}
var serilogLogger = configuration
.CreateLogger();
return new LoggerFactory().AddSerilog(serilogLogger);
}
var configuration = new LoggerConfiguration()
.MinimumLevel.Is(ConsoleVerbosityLevel < LogFileLevel ? ConsoleVerbosityLevel : LogFileLevel);
if (!string.IsNullOrEmpty(LogFilePath)) configuration = configuration.WriteTo.File(LogFilePath, LogFileLevel);
if (!DisableConsoleOutput) configuration = configuration.WriteTo.Console(ConsoleVerbosityLevel);
var serilogLogger = configuration
.CreateLogger();
return new LoggerFactory().AddSerilog(serilogLogger);
}
}

Просмотреть файл

@ -11,258 +11,244 @@ using Microsoft.CST.OAT;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Base class for a set of <see cref="Rule" /> objects to be operated on by the <see cref="RuleProcessor" />. This is
/// the abstract class used to allow for the default <see cref="RuleSet" /> and <see cref="TypedRuleSet{T}" /> for use
/// with rules that have extra properties.
/// </summary>
public abstract class AbstractRuleSet
{
protected readonly List<ConvertedOatRule> _oatRules = new();
private readonly Regex _searchInRegex = new("\\((.*),(.*)\\)", RegexOptions.Compiled);
protected ILogger _logger = NullLogger.Instance;
protected IEnumerable<Rule> _rules => _oatRules.Select(x => x.AppInspectorRule);
/// <summary>
/// Base class for a set of <see cref="Rule"/> objects to be operated on by the <see cref="RuleProcessor"/>. This is the abstract class used to allow for the default <see cref="RuleSet"/> and <see cref="TypedRuleSet{T}"/> for use with rules that have extra properties.
/// Filters rules within Ruleset by language
/// </summary>
public abstract class AbstractRuleSet
/// <param name="language"></param>
/// <returns> Filtered rules </returns>
public IEnumerable<ConvertedOatRule> ByLanguage(string language)
{
protected ILogger _logger = NullLogger.Instance;
protected readonly List<ConvertedOatRule> _oatRules = new();
protected IEnumerable<Rule> _rules { get => _oatRules.Select(x => x.AppInspectorRule); }
private readonly Regex _searchInRegex = new("\\((.*),(.*)\\)", RegexOptions.Compiled);
if (!string.IsNullOrEmpty(language))
return _oatRules.Where(x =>
x.AppInspectorRule.AppliesTo is { } appliesList && appliesList.Contains(language));
return Array.Empty<ConvertedOatRule>();
}
/// <summary>
/// Filters rules within Ruleset by language
/// </summary>
/// <param name="language"></param>
/// <returns> Filtered rules </returns>
public IEnumerable<ConvertedOatRule> ByLanguage(string language)
{
if (!string.IsNullOrEmpty(language))
/// <summary>
/// Filters rules within Ruleset filename
/// </summary>
/// <param name="input"></param>
/// <returns> Filtered rules </returns>
public IEnumerable<ConvertedOatRule> ByFilename(string input)
{
if (!string.IsNullOrEmpty(input))
return _oatRules.Where(x => x.AppInspectorRule.CompiledFileRegexes.Any(y => y.IsMatch(input)));
return Array.Empty<ConvertedOatRule>();
}
/// <summary>
/// Get the set of rules that apply to all files
/// </summary>
/// <returns></returns>
public IEnumerable<ConvertedOatRule> GetUniversalRules()
{
return _oatRules.Where(x =>
(x.AppInspectorRule.FileRegexes is null || x.AppInspectorRule.FileRegexes.Length == 0) &&
(x.AppInspectorRule.AppliesTo is null || x.AppInspectorRule.AppliesTo.Length == 0));
}
/// <summary>
/// Convert an AppInspector rule into an OAT rule.
/// </summary>
/// <param name="rule">The <see cref="Rule" /> to convert.</param>
/// <returns>A <see cref="ConvertedOatRule" /> if the AI rule was valid otherwise null.</returns>
public ConvertedOatRule? AppInspectorRuleToOatRule(Rule rule)
{
var clauses = new List<Clause>();
var clauseNumber = 0;
var expression = new StringBuilder("(");
foreach (var pattern in rule.Patterns)
if (GenerateClause(pattern, clauseNumber) is { } clause)
{
return _oatRules.Where(x => x.AppInspectorRule.AppliesTo is { } appliesList && appliesList.Contains(language));
}
return Array.Empty<ConvertedOatRule>();
}
/// <summary>
/// Filters rules within Ruleset filename
/// </summary>
/// <param name="input"></param>
/// <returns> Filtered rules </returns>
public IEnumerable<ConvertedOatRule> ByFilename(string input)
{
if (!string.IsNullOrEmpty(input))
{
return _oatRules.Where(x => x.AppInspectorRule.CompiledFileRegexes.Any(y => y.IsMatch(input)));
}
return Array.Empty<ConvertedOatRule>();
}
/// <summary>
/// Get the set of rules that apply to all files
/// </summary>
/// <returns></returns>
public IEnumerable<ConvertedOatRule> GetUniversalRules()
{
return _oatRules.Where(x => (x.AppInspectorRule.FileRegexes is null || x.AppInspectorRule.FileRegexes.Length == 0) && (x.AppInspectorRule.AppliesTo is null || x.AppInspectorRule.AppliesTo.Length == 0));
}
/// <summary>
/// Convert an AppInspector rule into an OAT rule.
/// </summary>
/// <param name="rule">The <see cref="Rule"/> to convert.</param>
/// <returns>A <see cref="ConvertedOatRule"/> if the AI rule was valid otherwise null.</returns>
public ConvertedOatRule? AppInspectorRuleToOatRule(Rule rule)
{
var clauses = new List<Clause>();
int clauseNumber = 0;
var expression = new StringBuilder("(");
foreach (var pattern in rule.Patterns)
{
if (GenerateClause(pattern, clauseNumber) is { } clause)
{
clauses.Add(clause);
if (clauseNumber > 0)
{
expression.Append(" OR ");
}
expression.Append(clauseNumber);
clauseNumber++;
}
else
{
_logger.LogWarning("Clause could not be generated from pattern {pattern}", pattern.Pattern);
}
}
if (clauses.Count > 0)
{
expression.Append(')');
clauses.Add(clause);
if (clauseNumber > 0) expression.Append(" OR ");
expression.Append(clauseNumber);
clauseNumber++;
}
else
{
return new ConvertedOatRule(rule.Id, rule);
_logger.LogWarning("Clause could not be generated from pattern {pattern}", pattern.Pattern);
}
foreach (var condition in rule.Conditions ?? Array.Empty<SearchCondition>())
if (clauses.Count > 0)
expression.Append(')');
else
return new ConvertedOatRule(rule.Id, rule);
foreach (var condition in rule.Conditions ?? Array.Empty<SearchCondition>())
{
var clause = GenerateCondition(condition, clauseNumber);
if (clause is { })
{
Clause? clause = GenerateCondition(condition, clauseNumber);
if (clause is { })
{
clauses.Add(clause);
expression.Append(" AND ");
expression.Append(clauseNumber);
clauseNumber++;
}
clauses.Add(clause);
expression.Append(" AND ");
expression.Append(clauseNumber);
clauseNumber++;
}
return new ConvertedOatRule(rule.Id, rule)
{
Clauses = clauses,
Expression = expression.ToString()
};
}
private Clause? GenerateCondition(SearchCondition condition, int clauseNumber)
return new ConvertedOatRule(rule.Id, rule)
{
if (condition.Pattern is {} conditionPattern)
Clauses = clauses,
Expression = expression.ToString()
};
}
private Clause? GenerateCondition(SearchCondition condition, int clauseNumber)
{
if (condition.Pattern is { } conditionPattern)
{
var subClause = GenerateClause(conditionPattern);
if (subClause is null)
{
var subClause = GenerateClause(conditionPattern);
if (subClause is null)
_logger.LogWarning("SubClause for condition could not be generated");
}
else
{
if (condition.SearchIn?.Equals("finding-only", StringComparison.InvariantCultureIgnoreCase) != false)
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
FindingOnly = true,
Invert = condition.NegateFinding
};
if (condition.SearchIn.StartsWith("finding-region", StringComparison.InvariantCultureIgnoreCase))
{
_logger.LogWarning("SubClause for condition could not be generated");
var argList = new List<int>();
var m = _searchInRegex.Match(condition.SearchIn);
if (m.Success)
for (var i = 1; i < m.Groups.Count; i++)
if (int.TryParse(m.Groups[i].Value, out var value))
argList.Add(value);
else
break;
if (argList.Count == 2)
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
FindingRegion = true,
Before = argList[0],
After = argList[1],
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("same-line", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
SameLineOnly = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("same-file", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
SameFile = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("only-before", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
OnlyBefore = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("only-after", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
OnlyAfter = true,
Invert = condition.NegateFinding
};
}
else
{
if (condition.SearchIn?.Equals("finding-only", StringComparison.InvariantCultureIgnoreCase) != false)
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
FindingOnly = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.StartsWith("finding-region", StringComparison.InvariantCultureIgnoreCase))
{
var argList = new List<int>();
Match m = _searchInRegex.Match(condition.SearchIn);
if (m.Success)
{
for (int i = 1; i < m.Groups.Count; i++)
{
if (int.TryParse(m.Groups[i].Value, out int value))
{
argList.Add(value);
}
else
{
break;
}
}
}
if (argList.Count == 2)
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
FindingRegion = true,
Before = argList[0],
After = argList[1],
Invert = condition.NegateFinding
};
}
}
else if (condition.SearchIn.Equals("same-line", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
SameLineOnly = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("same-file", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
SameFile = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("only-before", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
OnlyBefore = true,
Invert = condition.NegateFinding
};
}
else if (condition.SearchIn.Equals("only-after", StringComparison.InvariantCultureIgnoreCase))
{
return new WithinClause(subClause)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),
OnlyAfter = true,
Invert = condition.NegateFinding
};
}
else
{
_logger.LogWarning("Search condition {Condition} is not one of the accepted values and this condition will be ignored", condition.SearchIn);
}
}
}
return null;
}
private Clause? GenerateClause(SearchPattern pattern, int clauseNumber = -1)
{
if (pattern.Pattern != null)
{
var scopes = pattern.Scopes ?? new PatternScope[] { PatternScope.All };
var modifiers = pattern.Modifiers?.ToList() ?? new List<string>();
if (pattern.PatternType is PatternType.String or PatternType.Substring)
{
return new OatSubstringIndexClause(scopes, useWordBoundaries: pattern.PatternType == PatternType.String, xPaths: pattern.XPaths, jsonPaths:pattern.JsonPaths)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),//important to pattern index identification
Data = new List<string>() { pattern.Pattern },
Capture = true,
Arguments = pattern.Modifiers?.ToList() ?? new List<string>()
};
}
else if (pattern.PatternType == PatternType.Regex)
{
return new OatRegexWithIndexClause(scopes, null, pattern.XPaths, pattern.JsonPaths)
{
Label = clauseNumber.ToString(CultureInfo
.InvariantCulture), //important to pattern index identification
Data = new List<string>() { pattern.Pattern },
Capture = true,
Arguments = modifiers
};
}
else if (pattern.PatternType == PatternType.RegexWord)
{
return new OatRegexWithIndexClause(scopes, null, pattern.XPaths, pattern.JsonPaths)
{
Label = clauseNumber.ToString(CultureInfo.InvariantCulture),//important to pattern index identification
Data = new List<string>() { $"\\b({pattern.Pattern})\\b" },
Capture = true,
Arguments = pattern.Modifiers?.ToList() ?? new List<string>(),
};
_logger.LogWarning(
"Search condition {Condition} is not one of the accepted values and this condition will be ignored",
condition.SearchIn);
}
}
return null;
}
/// <summary>
/// Get the OAT Rules used in this RuleSet.
/// </summary>
/// <returns></returns>
public IEnumerable<ConvertedOatRule> GetOatRules() => _oatRules;
/// <summary>
/// Get the AppInspector Rules contained in this RuleSet.
/// </summary>
/// <returns></returns>
public IEnumerable<Rule> GetAppInspectorRules() => _rules;
return null;
}
}
private Clause? GenerateClause(SearchPattern pattern, int clauseNumber = -1)
{
if (pattern.Pattern != null)
{
var scopes = pattern.Scopes ?? new[] { PatternScope.All };
var modifiers = pattern.Modifiers?.ToList() ?? new List<string>();
if (pattern.PatternType is PatternType.String or PatternType.Substring)
return new OatSubstringIndexClause(scopes, useWordBoundaries: pattern.PatternType == PatternType.String,
xPaths: pattern.XPaths, jsonPaths: pattern.JsonPaths)
{
Label = clauseNumber.ToString(CultureInfo
.InvariantCulture), //important to pattern index identification
Data = new List<string> { pattern.Pattern },
Capture = true,
Arguments = pattern.Modifiers?.ToList() ?? new List<string>()
};
if (pattern.PatternType == PatternType.Regex)
return new OatRegexWithIndexClause(scopes, null, pattern.XPaths, pattern.JsonPaths)
{
Label = clauseNumber.ToString(CultureInfo
.InvariantCulture), //important to pattern index identification
Data = new List<string> { pattern.Pattern },
Capture = true,
Arguments = modifiers
};
if (pattern.PatternType == PatternType.RegexWord)
return new OatRegexWithIndexClause(scopes, null, pattern.XPaths, pattern.JsonPaths)
{
Label = clauseNumber.ToString(CultureInfo
.InvariantCulture), //important to pattern index identification
Data = new List<string> { $"\\b({pattern.Pattern})\\b" },
Capture = true,
Arguments = pattern.Modifiers?.ToList() ?? new List<string>()
};
}
return null;
}
/// <summary>
/// Get the OAT Rules used in this RuleSet.
/// </summary>
/// <returns></returns>
public IEnumerable<ConvertedOatRule> GetOatRules()
{
return _oatRules;
}
/// <summary>
/// Get the AppInspector Rules contained in this RuleSet.
/// </summary>
/// <returns></returns>
public IEnumerable<Rule> GetAppInspectorRules()
{
return _rules;
}
}

Просмотреть файл

@ -1,60 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.RulesEngine</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.RulesEngine</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.RulesEngine</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.RulesEngine</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.RulesEngine</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.RulesEngine</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonCons.JsonPath" Version="1.1.0" />
<PackageReference Include="Microsoft.CST.OAT" Version="1.2.25" />
<PackageReference Include="Microsoft.CST.RecursiveExtractor" Version="1.1.11" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JsonCons.JsonPath" Version="1.1.0"/>
<PackageReference Include="Microsoft.CST.OAT" Version="1.2.25"/>
<PackageReference Include="Microsoft.CST.RecursiveExtractor" Version="1.1.11"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
<ItemGroup>
<Content Remove="Resources\comments.json" />
<Content Remove="Resources\languages.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\comments.json" />
<EmbeddedResource Include="Resources\languages.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Common\AppInspector.Common.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="..\LICENSE.txt" Pack="true" PackagePath="" />
<None Include="..\icon-128.png" Pack="true" PackagePath="" />
</ItemGroup>
</ItemGroup>
<ItemGroup>
<Content Remove="Resources\comments.json"/>
<Content Remove="Resources\languages.json"/>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\comments.json"/>
<EmbeddedResource Include="Resources\languages.json"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Common\AppInspector.Common.csproj"/>
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig"/>
<None Include="..\LICENSE.txt" Pack="true" PackagePath=""/>
<None Include="..\icon-128.png" Pack="true" PackagePath=""/>
</ItemGroup>
</Project>

Просмотреть файл

@ -1,21 +1,20 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Class represents boundary in text
/// </summary>
public class Boundary
{
/// <summary>
/// Class represents boundary in text
/// Starting position
/// </summary>
public class Boundary
{
/// <summary>
/// Starting position
/// </summary>
public int Index { get; set; }
public int Index { get; set; }
/// <summary>
/// Length of boundary
/// </summary>
public int Length { get; set; }
}
/// <summary>
/// Length of boundary
/// </summary>
public int Length { get; set; }
}

Просмотреть файл

@ -3,23 +3,22 @@
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Comment class to hold information about comment for each language
/// </summary>
internal class Comment
{
/// <summary>
/// Comment class to hold information about comment for each language
/// </summary>
internal class Comment
{
[JsonProperty(PropertyName ="language")]
public string[]? Languages { get; set; }
[JsonProperty(PropertyName = "language")]
public string[]? Languages { get; set; }
[JsonProperty(PropertyName ="inline")]
public string? Inline { get; set; }
[JsonProperty(PropertyName = "inline")]
public string? Inline { get; set; }
[JsonProperty(PropertyName ="prefix")]
public string? Prefix { get; set; }
[JsonProperty(PropertyName = "prefix")]
public string? Prefix { get; set; }
[JsonProperty(PropertyName ="suffix")]
public string? Suffix { get; set; }
}
[JsonProperty(PropertyName = "suffix")]
public string? Suffix { get; set; }
}

Просмотреть файл

@ -1,11 +1,15 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
{
using System;
namespace Microsoft.ApplicationInspector.RulesEngine;
[Flags]
[JsonConverter(typeof(StringEnumConverter))]
public enum Confidence { Unspecified = 0, Low = 1, Medium = 2, High = 4 }
[Flags]
[JsonConverter(typeof(StringEnumConverter))]
public enum Confidence
{
Unspecified = 0,
Low = 1,
Medium = 2,
High = 4
}

Просмотреть файл

@ -4,26 +4,28 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Content Type class
/// </summary>
public class LanguageInfo
{
/// <summary>
/// Content Type class
/// </summary>
public class LanguageInfo
public enum LangFileType
{
public enum LangFileType { Code, Build };
[JsonProperty(PropertyName ="name")]
public string Name { get; set; } = "";
[JsonProperty(PropertyName ="extensions")]
public string[]? Extensions { get; set; }
[JsonProperty(PropertyName ="file-names")]
public string[]? FileNames { get; set; }
[JsonProperty(PropertyName ="type")]
[JsonConverter(typeof(StringEnumConverter))]
public LangFileType Type { get; set; } = LangFileType.Code;
Code,
Build
}
[JsonProperty(PropertyName = "name")] public string Name { get; set; } = "";
[JsonProperty(PropertyName = "extensions")]
public string[]? Extensions { get; set; }
[JsonProperty(PropertyName = "file-names")]
public string[]? FileNames { get; set; }
[JsonProperty(PropertyName = "type")]
[JsonConverter(typeof(StringEnumConverter))]
public LangFileType Type { get; set; } = LangFileType.Code;
}

Просмотреть файл

@ -1,201 +1,193 @@
// Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Helper class for language based commenting
/// </summary>
public sealed class Languages
{
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
private const string CommentResourcePath = "Microsoft.ApplicationInspector.RulesEngine.Resources.comments.json";
private const string LanguagesResourcePath = "Microsoft.ApplicationInspector.RulesEngine.Resources.languages.json";
private readonly List<Comment> _comments;
private readonly List<LanguageInfo> _languageInfos;
private readonly ILogger _logger;
/// <summary>
/// Helper class for language based commenting
/// </summary>
public sealed class Languages
public Languages(ILoggerFactory? loggerFactory = null, Stream? commentsStream = null,
Stream? languagesStream = null)
{
private readonly List<Comment> _comments;
private readonly List<LanguageInfo> _languageInfos;
private readonly ILogger _logger;
_logger = loggerFactory?.CreateLogger<Languages>() ?? new NullLogger<Languages>();
var assembly = typeof(Languages).Assembly;
var commentResource = commentsStream ?? assembly.GetManifestResourceStream(CommentResourcePath);
private const string CommentResourcePath = "Microsoft.ApplicationInspector.RulesEngine.Resources.comments.json";
private const string LanguagesResourcePath = "Microsoft.ApplicationInspector.RulesEngine.Resources.languages.json";
public Languages(ILoggerFactory? loggerFactory = null, Stream? commentsStream = null, Stream? languagesStream = null)
if (commentResource is null)
{
_logger = loggerFactory?.CreateLogger<Languages>() ?? new NullLogger<Languages>();
Assembly assembly = typeof(Languages).Assembly;
Stream? commentResource = commentsStream ?? assembly.GetManifestResourceStream(CommentResourcePath);
if (commentResource is null)
{
_logger.LogError("Failed to load embedded comments configuration from {CommentResourcePath}", CommentResourcePath);
_comments = new List<Comment>();
}
else
{
using StreamReader commentStreamReader = new(commentResource);
using JsonReader commentJsonReader = new JsonTextReader(commentStreamReader);
JsonSerializer jsonSerializer = new();
_comments = jsonSerializer.Deserialize<List<Comment>>(commentJsonReader) ?? new List<Comment>();
}
Stream? languagesResource = languagesStream ?? assembly.GetManifestResourceStream(LanguagesResourcePath);
if (languagesResource is null)
{
_logger.LogError("Failed to load embedded languages configuration from {LanguagesResourcePath}", LanguagesResourcePath);
_languageInfos = new List<LanguageInfo>();
}
else
{
using StreamReader languagesStreamReader = new(languagesResource);
using JsonReader languagesJsonReader = new JsonTextReader(languagesStreamReader);
JsonSerializer jsonSerializer = new();
_languageInfos = jsonSerializer.Deserialize<List<LanguageInfo>>(languagesJsonReader) ?? new List<LanguageInfo>();
}
_logger.LogError("Failed to load embedded comments configuration from {CommentResourcePath}",
CommentResourcePath);
_comments = new List<Comment>();
}
else
{
using StreamReader commentStreamReader = new(commentResource);
using JsonReader commentJsonReader = new JsonTextReader(commentStreamReader);
JsonSerializer jsonSerializer = new();
_comments = jsonSerializer.Deserialize<List<Comment>>(commentJsonReader) ?? new List<Comment>();
}
public static Languages FromConfigurationFiles(ILoggerFactory? loggerFactory = null, string? commentsPath = null, string? languagesPath = null)
var languagesResource = languagesStream ?? assembly.GetManifestResourceStream(LanguagesResourcePath);
if (languagesResource is null)
{
Stream? commentsStream = commentsPath is null ? null : File.OpenRead(commentsPath);
Stream? languagesStream = languagesPath is null ? null : File.OpenRead(languagesPath);
try
{
return new Languages(loggerFactory, commentsStream, languagesStream);
}
finally
{
commentsStream?.Dispose();
languagesStream?.Dispose();
}
_logger.LogError("Failed to load embedded languages configuration from {LanguagesResourcePath}",
LanguagesResourcePath);
_languageInfos = new List<LanguageInfo>();
}
/// <summary>
/// Returns the language for a given file name if detected.
/// </summary>
/// <param name="fileName">The FileName to check.</param>
/// <param name="info">If this returns true, a valid LanguageInfo object. If false, undefined.</param>
/// <returns>True if the language could be detected based on the filename.</returns>
public bool FromFileNameOut(string fileName, out LanguageInfo info)
else
{
info = new LanguageInfo();
return FromFileName(fileName, ref info);
}
/// <summary>
/// Returns <see cref="LanguageInfo"/> for a given filename. It is recommended to use <see cref="FromFileNameOut"/> instead.
/// </summary>
/// <param name="fileName">File name</param>
/// <param name="info">Ref to an existing LanguageInfo object to assign the result to, if true is returned.</param>
/// <returns>True if the language could be detected based on the filename</returns>
public bool FromFileName(string fileName, ref LanguageInfo info)
{
if (fileName == null)
{
return false;
}
string file = Path.GetFileName(fileName).ToLower(CultureInfo.CurrentCulture);
string ext = Path.GetExtension(file);
// Look for whole filename first
if (_languageInfos.FirstOrDefault(item => item.FileNames?.Contains(file,StringComparer.InvariantCultureIgnoreCase) ?? false) is LanguageInfo langInfo)
{
info = langInfo;
return true;
}
// Look for extension only ext is defined
if (!string.IsNullOrEmpty(ext))
{
if (_languageInfos.FirstOrDefault(item => item.Extensions?.Contains(ext,StringComparer.InvariantCultureIgnoreCase) ?? false) is LanguageInfo extLangInfo)
{
info = extLangInfo;
return true;
}
}
return false;
}
/// <summary>
/// Gets comment inline for given language
/// </summary>
/// <param name="language">Language</param>
/// <returns>Commented string</returns>
public string GetCommentInline(string language)
{
string result = string.Empty;
if (language != null)
{
foreach (Comment comment in _comments)
{
if (Array.Exists(comment.Languages ?? new string[] { "" }, x => x.Equals(language, StringComparison.InvariantCultureIgnoreCase)) && comment.Inline is { })
return comment.Inline;
}
}
return result;
}
/// <summary>
/// Gets comment preffix for given language
/// </summary>
/// <param name="language">Language</param>
/// <returns>Commented string</returns>
public string GetCommentPrefix(string language)
{
string result = string.Empty;
if (language != null)
{
foreach (Comment comment in _comments)
{
if ((comment.Languages?.Contains(language.ToLower(CultureInfo.InvariantCulture)) ?? false) && comment.Prefix is { })
return comment.Prefix;
}
}
return result;
}
/// <summary>
/// Gets comment suffix for given language
/// </summary>
/// <param name="language">Language</param>
/// <returns>Commented string</returns>
public string GetCommentSuffix(string language)
{
string result = string.Empty;
if (language != null)
{
foreach (Comment comment in _comments)
{
if (Array.Exists(comment.Languages ?? new string[] { "" }, x => x.Equals(language, StringComparison.InvariantCultureIgnoreCase)) && comment.Suffix is { })
return comment.Suffix;
}
}
return result;
}
/// <summary>
/// Get names of all known languages
/// </summary>
/// <returns>Returns list of names</returns>
public string[] GetNames()
{
var names = from x in _languageInfos
select x.Name;
return names.ToArray();
using StreamReader languagesStreamReader = new(languagesResource);
using JsonReader languagesJsonReader = new JsonTextReader(languagesStreamReader);
JsonSerializer jsonSerializer = new();
_languageInfos = jsonSerializer.Deserialize<List<LanguageInfo>>(languagesJsonReader) ??
new List<LanguageInfo>();
}
}
public static Languages FromConfigurationFiles(ILoggerFactory? loggerFactory = null, string? commentsPath = null,
string? languagesPath = null)
{
Stream? commentsStream = commentsPath is null ? null : File.OpenRead(commentsPath);
Stream? languagesStream = languagesPath is null ? null : File.OpenRead(languagesPath);
try
{
return new Languages(loggerFactory, commentsStream, languagesStream);
}
finally
{
commentsStream?.Dispose();
languagesStream?.Dispose();
}
}
/// <summary>
/// Returns the language for a given file name if detected.
/// </summary>
/// <param name="fileName">The FileName to check.</param>
/// <param name="info">If this returns true, a valid LanguageInfo object. If false, undefined.</param>
/// <returns>True if the language could be detected based on the filename.</returns>
public bool FromFileNameOut(string fileName, out LanguageInfo info)
{
info = new LanguageInfo();
return FromFileName(fileName, ref info);
}
/// <summary>
/// Returns <see cref="LanguageInfo" /> for a given filename. It is recommended to use <see cref="FromFileNameOut" />
/// instead.
/// </summary>
/// <param name="fileName">File name</param>
/// <param name="info">Ref to an existing LanguageInfo object to assign the result to, if true is returned.</param>
/// <returns>True if the language could be detected based on the filename</returns>
public bool FromFileName(string fileName, ref LanguageInfo info)
{
if (fileName == null) return false;
var file = Path.GetFileName(fileName).ToLower(CultureInfo.CurrentCulture);
var ext = Path.GetExtension(file);
// Look for whole filename first
if (_languageInfos.FirstOrDefault(item =>
item.FileNames?.Contains(file, StringComparer.InvariantCultureIgnoreCase) ?? false) is LanguageInfo
langInfo)
{
info = langInfo;
return true;
}
// Look for extension only ext is defined
if (!string.IsNullOrEmpty(ext))
if (_languageInfos.FirstOrDefault(item =>
item.Extensions?.Contains(ext, StringComparer.InvariantCultureIgnoreCase) ?? false) is LanguageInfo
extLangInfo)
{
info = extLangInfo;
return true;
}
return false;
}
/// <summary>
/// Gets comment inline for given language
/// </summary>
/// <param name="language">Language</param>
/// <returns>Commented string</returns>
public string GetCommentInline(string language)
{
var result = string.Empty;
if (language != null)
foreach (var comment in _comments)
if (Array.Exists(comment.Languages ?? new[] { "" },
x => x.Equals(language, StringComparison.InvariantCultureIgnoreCase)) && comment.Inline is { })
return comment.Inline;
return result;
}
/// <summary>
/// Gets comment preffix for given language
/// </summary>
/// <param name="language">Language</param>
/// <returns>Commented string</returns>
public string GetCommentPrefix(string language)
{
var result = string.Empty;
if (language != null)
foreach (var comment in _comments)
if ((comment.Languages?.Contains(language.ToLower(CultureInfo.InvariantCulture)) ?? false) &&
comment.Prefix is { })
return comment.Prefix;
return result;
}
/// <summary>
/// Gets comment suffix for given language
/// </summary>
/// <param name="language">Language</param>
/// <returns>Commented string</returns>
public string GetCommentSuffix(string language)
{
var result = string.Empty;
if (language != null)
foreach (var comment in _comments)
if (Array.Exists(comment.Languages ?? new[] { "" },
x => x.Equals(language, StringComparison.InvariantCultureIgnoreCase)) && comment.Suffix is { })
return comment.Suffix;
return result;
}
/// <summary>
/// Get names of all known languages
/// </summary>
/// <returns>Returns list of names</returns>
public string[] GetNames()
{
var names = from x in _languageInfos
select x.Name;
return names.ToArray();
}
}

Просмотреть файл

@ -1,11 +1,10 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
public class Location
{
public class Location
{
public int Line { get; set; } = 0;
public int Column { get; set; } = 0;
}
public int Line { get; set; } = 0;
public int Column { get; set; } = 0;
}

Просмотреть файл

@ -4,166 +4,158 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Represents augmented record of result issue from rules engine
/// </summary>
public class MatchRecord
{
/// <summary>
/// Represents augmented record of result issue from rules engine
/// Force required arg to avoid null rule
/// </summary>
public class MatchRecord
/// <param name="rule"></param>
public MatchRecord(Rule rule)
{
/// <summary>
/// Force required arg to avoid null rule
/// </summary>
/// <param name="rule"></param>
public MatchRecord(Rule rule)
{
Rule = rule;
RuleId = rule.Id;
RuleName = rule.Name;
RuleDescription = rule.Description;
Tags = rule.Tags;
Severity = rule.Severity;
}
[JsonConstructor]
public MatchRecord(string ruleId, string ruleName)
{
RuleId = ruleId;
RuleName = ruleName;
}
[JsonIgnore]
[ExcludeFromCodeCoverage]
public Rule? Rule { get; }
/// <summary>
/// Rule Id found in matching rule
/// </summary>
[JsonProperty(PropertyName ="ruleId")]
[ExcludeFromCodeCoverage]
public string RuleId { get; set; }
/// <summary>
/// Rule name found in matching rule
/// </summary>
[JsonProperty(PropertyName ="ruleName")]
[ExcludeFromCodeCoverage]
public string RuleName { get; set; }
/// <summary>
/// Rule description found in matching rule
/// </summary>
[JsonProperty(PropertyName ="ruleDescription")]
[ExcludeFromCodeCoverage]
public string? RuleDescription { get; set; }
/// <summary>
/// Tags in matching rule
/// </summary>
[JsonProperty(PropertyName ="tags")]
[ExcludeFromCodeCoverage]
public string[]? Tags { get; set; }
/// <summary>
/// Rule severity
/// </summary>_rule
[JsonProperty(PropertyName ="severity")]
[ExcludeFromCodeCoverage]
public Severity Severity { get; set; }
[JsonIgnore]
[ExcludeFromCodeCoverage]
public SearchPattern? MatchingPattern { get; set; }
/// <summary>
/// Matching pattern found in matching rule
/// </summary>
[JsonProperty(PropertyName ="pattern")]
[ExcludeFromCodeCoverage]
public string? Pattern => MatchingPattern?.Pattern;
/// <summary>
/// Pattern confidence in matching rule pattern
/// </summary>
[JsonProperty(PropertyName ="confidence")]
[ExcludeFromCodeCoverage]
public Confidence Confidence => MatchingPattern?.Confidence ?? Confidence.Unspecified;
/// <summary>
/// Pattern type of matching pattern
/// </summary>
[JsonProperty(PropertyName ="type")]
[ExcludeFromCodeCoverage]
public string? PatternType => MatchingPattern?.PatternType.ToString();
[JsonIgnore]
[ExcludeFromCodeCoverage]
public TextContainer? FullTextContainer { get; set; }
/// <summary>
/// Internal to namespace only
/// </summary>
[JsonIgnore]
[ExcludeFromCodeCoverage]
public LanguageInfo LanguageInfo { get; set; } = new LanguageInfo();
/// <summary>
/// Friendly source type
/// </summary>
[JsonProperty(PropertyName ="language")]
public string? Language => LanguageInfo?.Name;
/// <summary>
/// Filename of this match
/// </summary>
[JsonProperty(PropertyName ="fileName")]
[ExcludeFromCodeCoverage]
public string? FileName { get; set; }
/// <summary>
/// Matching text for this record
/// </summary>
[JsonProperty(PropertyName ="sample")]
[ExcludeFromCodeCoverage]
public string Sample { get; set; } = "";
/// <summary>
/// Matching surrounding context text for sample in this record
/// </summary>
[JsonProperty(PropertyName ="excerpt")]
[ExcludeFromCodeCoverage]
public string Excerpt { get; set; } = "";
[JsonIgnore]
[ExcludeFromCodeCoverage]
public Boundary Boundary { get; set; } = new Boundary();
/// <summary>
/// Starting line location of the matching text
/// </summary>
[JsonProperty(PropertyName ="startLocationLine")]
[ExcludeFromCodeCoverage]
public int StartLocationLine { get; set; }
/// <summary>
/// Starting column location of the matching text
/// </summary>
[JsonProperty(PropertyName ="startLocationColumn")]
[ExcludeFromCodeCoverage]
public int StartLocationColumn { get; set; }
/// <summary>
/// Ending line location of the matching text
/// </summary>
[JsonProperty(PropertyName ="endLocationLine")]
[ExcludeFromCodeCoverage]
public int EndLocationLine { get; set; }
/// <summary>
/// Ending column of the matching text
/// </summary>
[JsonProperty(PropertyName ="endLocationColumn")]
[ExcludeFromCodeCoverage]
public int EndLocationColumn { get; set; }
Rule = rule;
RuleId = rule.Id;
RuleName = rule.Name;
RuleDescription = rule.Description;
Tags = rule.Tags;
Severity = rule.Severity;
}
[JsonConstructor]
public MatchRecord(string ruleId, string ruleName)
{
RuleId = ruleId;
RuleName = ruleName;
}
[JsonIgnore] [ExcludeFromCodeCoverage] public Rule? Rule { get; }
/// <summary>
/// Rule Id found in matching rule
/// </summary>
[JsonProperty(PropertyName = "ruleId")]
[ExcludeFromCodeCoverage]
public string RuleId { get; set; }
/// <summary>
/// Rule name found in matching rule
/// </summary>
[JsonProperty(PropertyName = "ruleName")]
[ExcludeFromCodeCoverage]
public string RuleName { get; set; }
/// <summary>
/// Rule description found in matching rule
/// </summary>
[JsonProperty(PropertyName = "ruleDescription")]
[ExcludeFromCodeCoverage]
public string? RuleDescription { get; set; }
/// <summary>
/// Tags in matching rule
/// </summary>
[JsonProperty(PropertyName = "tags")]
[ExcludeFromCodeCoverage]
public string[]? Tags { get; set; }
/// <summary>
/// Rule severity
/// </summary>
/// _rule
[JsonProperty(PropertyName = "severity")]
[ExcludeFromCodeCoverage]
public Severity Severity { get; set; }
[JsonIgnore] [ExcludeFromCodeCoverage] public SearchPattern? MatchingPattern { get; set; }
/// <summary>
/// Matching pattern found in matching rule
/// </summary>
[JsonProperty(PropertyName = "pattern")]
[ExcludeFromCodeCoverage]
public string? Pattern => MatchingPattern?.Pattern;
/// <summary>
/// Pattern confidence in matching rule pattern
/// </summary>
[JsonProperty(PropertyName = "confidence")]
[ExcludeFromCodeCoverage]
public Confidence Confidence => MatchingPattern?.Confidence ?? Confidence.Unspecified;
/// <summary>
/// Pattern type of matching pattern
/// </summary>
[JsonProperty(PropertyName = "type")]
[ExcludeFromCodeCoverage]
public string? PatternType => MatchingPattern?.PatternType.ToString();
[JsonIgnore] [ExcludeFromCodeCoverage] public TextContainer? FullTextContainer { get; set; }
/// <summary>
/// Internal to namespace only
/// </summary>
[JsonIgnore]
[ExcludeFromCodeCoverage]
public LanguageInfo LanguageInfo { get; set; } = new();
/// <summary>
/// Friendly source type
/// </summary>
[JsonProperty(PropertyName = "language")]
public string? Language => LanguageInfo?.Name;
/// <summary>
/// Filename of this match
/// </summary>
[JsonProperty(PropertyName = "fileName")]
[ExcludeFromCodeCoverage]
public string? FileName { get; set; }
/// <summary>
/// Matching text for this record
/// </summary>
[JsonProperty(PropertyName = "sample")]
[ExcludeFromCodeCoverage]
public string Sample { get; set; } = "";
/// <summary>
/// Matching surrounding context text for sample in this record
/// </summary>
[JsonProperty(PropertyName = "excerpt")]
[ExcludeFromCodeCoverage]
public string Excerpt { get; set; } = "";
[JsonIgnore] [ExcludeFromCodeCoverage] public Boundary Boundary { get; set; } = new();
/// <summary>
/// Starting line location of the matching text
/// </summary>
[JsonProperty(PropertyName = "startLocationLine")]
[ExcludeFromCodeCoverage]
public int StartLocationLine { get; set; }
/// <summary>
/// Starting column location of the matching text
/// </summary>
[JsonProperty(PropertyName = "startLocationColumn")]
[ExcludeFromCodeCoverage]
public int StartLocationColumn { get; set; }
/// <summary>
/// Ending line location of the matching text
/// </summary>
[JsonProperty(PropertyName = "endLocationLine")]
[ExcludeFromCodeCoverage]
public int EndLocationLine { get; set; }
/// <summary>
/// Ending column of the matching text
/// </summary>
[JsonProperty(PropertyName = "endLocationColumn")]
[ExcludeFromCodeCoverage]
public int EndLocationColumn { get; set; }
}

Просмотреть файл

@ -1,15 +1,14 @@
using Microsoft.CST.OAT;
using Microsoft.Extensions.Logging;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
public class ApplicationInspectorAnalyzer : Analyzer
{
public class ApplicationInspectorAnalyzer : Analyzer
public ApplicationInspectorAnalyzer(ILoggerFactory? loggerFactory = null)
{
public ApplicationInspectorAnalyzer(ILoggerFactory? loggerFactory = null)
{
SetOperation(new WithinOperation(this, loggerFactory));
SetOperation(new OatRegexWithIndexOperation(this, loggerFactory));
SetOperation(new OatSubstringIndexOperation(this, loggerFactory));
}
SetOperation(new WithinOperation(this, loggerFactory));
SetOperation(new OatRegexWithIndexOperation(this, loggerFactory));
SetOperation(new OatSubstringIndexOperation(this, loggerFactory));
}
}
}

Просмотреть файл

@ -1,18 +1,17 @@
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
{
/// <summary>
/// Wrapper for OAT based processing
/// </summary>
public class ConvertedOatRule : CST.OAT.Rule
{
public ConvertedOatRule(string name, Rule rule) : base(name)
{
AppInspectorRule = rule;
}
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
/// <summary>
/// Native Application Inspector Rule to preserve format and instance data
/// </summary>
public Rule AppInspectorRule { get; }
/// <summary>
/// Wrapper for OAT based processing
/// </summary>
public class ConvertedOatRule : CST.OAT.Rule
{
public ConvertedOatRule(string name, Rule rule) : base(name)
{
AppInspectorRule = rule;
}
}
/// <summary>
/// Native Application Inspector Rule to preserve format and instance data
/// </summary>
public Rule AppInspectorRule { get; }
}

Просмотреть файл

@ -2,22 +2,22 @@
using Microsoft.CST.OAT;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
public class OatRegexWithIndexClause : Clause
{
public class OatRegexWithIndexClause : Clause
public OatRegexWithIndexClause(PatternScope[] scopes, string? field = null, string[]? xPaths = null,
string[]? jsonPaths = null) : base(Operation.Custom, field)
{
public OatRegexWithIndexClause(PatternScope[] scopes, string? field = null, string[]? xPaths = null, string[]? jsonPaths = null) : base(Operation.Custom, field)
{
Scopes = scopes;
CustomOperation = "RegexWithIndex";
XPaths = xPaths;
JsonPaths = jsonPaths;
}
public string[]? JsonPaths { get; }
public string[]? XPaths { get; }
public PatternScope[] Scopes { get; }
Scopes = scopes;
CustomOperation = "RegexWithIndex";
XPaths = xPaths;
JsonPaths = jsonPaths;
}
public string[]? JsonPaths { get; }
public string[]? XPaths { get; }
public PatternScope[] Scopes { get; }
}

Просмотреть файл

@ -9,180 +9,162 @@ using Microsoft.CST.OAT.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
/// <summary>
/// The Custom Operation to enable identification of pattern index in result used by Application Inspector to report
/// why a given
/// result was matched and to retrieve other pattern level meta-data
/// </summary>
public class OatRegexWithIndexOperation : OatOperation
{
private readonly ILogger<OatRegexWithIndexOperation> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly ConcurrentDictionary<(string, RegexOptions), Regex?> RegexCache = new();
/// <summary>
/// The Custom Operation to enable identification of pattern index in result used by Application Inspector to report why a given
/// result was matched and to retrieve other pattern level meta-data
/// Create an OatOperation given an analyzer
/// </summary>
public class OatRegexWithIndexOperation : OatOperation
/// <param name="analyzer">The analyzer context to work with</param>
/// <param name="loggerFactory">Logger Factory to use</param>
public OatRegexWithIndexOperation(Analyzer analyzer, ILoggerFactory? loggerFactory = null) : base(Operation.Custom,
analyzer)
{
private readonly ConcurrentDictionary<(string, RegexOptions), Regex?> RegexCache = new();
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<OatRegexWithIndexOperation> _logger;
_loggerFactory = loggerFactory ?? new NullLoggerFactory();
_logger = _loggerFactory.CreateLogger<OatRegexWithIndexOperation>();
CustomOperation = "RegexWithIndex";
OperationDelegate = RegexWithIndexOperationDelegate;
ValidationDelegate = RegexWithIndexValidationDelegate;
}
/// <summary>
/// Create an OatOperation given an analyzer
/// </summary>
/// <param name="analyzer">The analyzer context to work with</param>
/// <param name="loggerFactory">Logger Factory to use</param>
public OatRegexWithIndexOperation(Analyzer analyzer, ILoggerFactory? loggerFactory = null) : base(Operation.Custom, analyzer)
private static IEnumerable<Violation> RegexWithIndexValidationDelegate(CST.OAT.Rule rule, Clause clause)
{
if (clause.Data?.Count is null or 0)
yield return new Violation(
string.Format(Strings.Get("Err_ClauseNoData"), rule.Name,
clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)), rule, clause);
else if (clause.Data is List<string> regexList)
foreach (var regex in regexList)
if (!Helpers.IsValidRegex(regex))
yield return new Violation(
string.Format(Strings.Get("Err_ClauseInvalidRegex"), rule.Name,
clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture), regex),
rule, clause);
if (clause.DictData?.Count > 0)
yield return new Violation(
string.Format(Strings.Get("Err_ClauseDictDataUnexpected"), rule.Name,
clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture),
clause.Operation.ToString()), rule, clause);
}
/// <summary>
/// Returns results with pattern index and Boundary as a tuple to enable retrieval of Rule pattern level meta-data like
/// Confidence and report the
/// pattern that was responsible for the match
/// </summary>
/// <param name="clause"></param>
/// <param name="state1"></param>
/// <param name="state2"></param>
/// <param name="captures"></param>
/// <returns></returns>
private OperationResult RegexWithIndexOperationDelegate(Clause clause, object? state1, object? state2,
IEnumerable<ClauseCapture>? captures)
{
if (state1 is TextContainer tc && clause is OatRegexWithIndexClause src &&
clause.Data is List<string> RegexList && RegexList.Count > 0)
{
_loggerFactory = loggerFactory ?? new NullLoggerFactory();
_logger = _loggerFactory.CreateLogger<OatRegexWithIndexOperation>();
CustomOperation = "RegexWithIndex";
OperationDelegate = RegexWithIndexOperationDelegate;
ValidationDelegate = RegexWithIndexValidationDelegate;
}
private static IEnumerable<Violation> RegexWithIndexValidationDelegate(CST.OAT.Rule rule, Clause clause)
{
if (clause.Data?.Count is null or 0)
RegexOptions regexOpts = new();
var subBoundary = state2 is Boundary s2 ? s2 : null;
if (src.Arguments.Contains("i")) regexOpts |= RegexOptions.IgnoreCase;
if (src.Arguments.Contains("m")) regexOpts |= RegexOptions.Multiline;
List<(int, Boundary)> outmatches = new(); //tuple results i.e. pattern index and where
if (Analyzer != null)
{
yield return new Violation(string.Format(Strings.Get("Err_ClauseNoData"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)), rule, clause);
}
else if (clause.Data is List<string> regexList)
{
foreach (var regex in regexList)
{
if (!Helpers.IsValidRegex(regex))
foreach (var regexString in RegexList)
if (StringToRegex(regexString, regexOpts) is { } regex)
{
yield return new Violation(string.Format(Strings.Get("Err_ClauseInvalidRegex"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture), regex), rule, clause);
}
}
}
if (clause.DictData?.Count > 0)
{
yield return new Violation(string.Format(Strings.Get("Err_ClauseDictDataUnexpected"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture), clause.Operation.ToString()), rule, clause);
}
}
if (src.XPaths is not null)
foreach (var xmlPath in src.XPaths)
{
var targets = tc.GetStringFromXPath(xmlPath);
foreach (var target in targets)
{
var matches = GetMatches(regex, tc, clause, src.Scopes, target.Item2);
foreach (var match in matches) outmatches.Add(match);
}
}
/// <summary>
/// Returns results with pattern index and Boundary as a tuple to enable retrieval of Rule pattern level meta-data like Confidence and report the
/// pattern that was responsible for the match
/// </summary>
/// <param name="clause"></param>
/// <param name="state1"></param>
/// <param name="state2"></param>
/// <param name="captures"></param>
/// <returns></returns>
private OperationResult RegexWithIndexOperationDelegate(Clause clause, object? state1, object? state2, IEnumerable<ClauseCapture>? captures)
{
if (state1 is TextContainer tc && clause is OatRegexWithIndexClause src && clause.Data is List<string> RegexList && RegexList.Count > 0)
{
RegexOptions regexOpts = new();
Boundary? subBoundary = state2 is Boundary s2 ? s2 : null;
if (src.JsonPaths is not null)
foreach (var jsonPath in src.JsonPaths)
{
var targets = tc.GetStringFromJsonPath(jsonPath);
foreach (var target in targets)
outmatches.AddRange(GetMatches(regex, tc, clause, src.Scopes, target.Item2));
}
if (src.Arguments.Contains("i"))
{
regexOpts |= RegexOptions.IgnoreCase;
}
if (src.Arguments.Contains("m"))
{
regexOpts |= RegexOptions.Multiline;
}
List<(int, Boundary)> outmatches = new();//tuple results i.e. pattern index and where
if (Analyzer != null)
{
foreach (var regexString in RegexList)
{
if (StringToRegex(regexString, regexOpts) is { } regex)
if (src.JsonPaths is null && src.XPaths is null)
{
if (src.XPaths is not null)
{
foreach (var xmlPath in src.XPaths)
{
var targets = tc.GetStringFromXPath(xmlPath);
foreach (var target in targets)
{
var matches = GetMatches(regex, tc, clause, src.Scopes, target.Item2);
foreach (var match in matches)
{
outmatches.Add(match);
}
}
}
}
if (src.JsonPaths is not null)
{
foreach (var jsonPath in src.JsonPaths)
{
var targets = tc.GetStringFromJsonPath(jsonPath);
foreach (var target in targets)
{
outmatches.AddRange(GetMatches(regex, tc, clause, src.Scopes, target.Item2));
}
}
}
if (src.JsonPaths is null && src.XPaths is null)
{
if (subBoundary is not null)
{
outmatches.AddRange(GetMatches(regex, tc, clause, src.Scopes, subBoundary));
}
else
{
outmatches.AddRange(GetMatches(regex, tc, clause, src.Scopes, null));
}
}
}
if (subBoundary is not null)
outmatches.AddRange(GetMatches(regex, tc, clause, src.Scopes, subBoundary));
else
outmatches.AddRange(GetMatches(regex, tc, clause, src.Scopes, null));
}
}
var result = src.Invert ? outmatches.Count == 0 : outmatches.Count > 0;
return new OperationResult(result, result && src.Capture ? new TypedClauseCapture<List<(int, Boundary)>>(clause, outmatches, state1) : null);
}
var result = src.Invert ? outmatches.Count == 0 : outmatches.Count > 0;
return new OperationResult(result,
result && src.Capture
? new TypedClauseCapture<List<(int, Boundary)>>(clause, outmatches, state1)
: null);
}
return new OperationResult(false, null);
}
private IEnumerable<(int, Boundary)> GetMatches(Regex regex, TextContainer tc, Clause clause, PatternScope[] scopes, Boundary? boundary)
{
foreach (var match in regex.Matches(boundary is null ? tc.FullContent : tc.GetBoundaryText(boundary)))
{
if (match is Match m)
{
Boundary translatedBoundary = new()
{
Length = m.Length,
Index = m.Index + (boundary?.Index ?? 0)
};
//regex patterns will be indexed off data while string patterns result in N clauses
int patternIndex = Convert.ToInt32(clause.Label);
return new OperationResult(false);
}
// Should return only scoped matches
if (tc.ScopeMatch(scopes, translatedBoundary))
{
yield return (patternIndex, translatedBoundary);
}
}
}
}
/// <summary>
/// Converts a strings to a compiled regex.
/// Uses an internal cache.
/// </summary>
/// <param name="built">The regex to build</param>
/// <param name="regexOptions">The options to use.</param>
/// <returns>The built Regex</returns>
private Regex? StringToRegex(string built, RegexOptions regexOptions)
{
if (!RegexCache.ContainsKey((built, regexOptions)))
private IEnumerable<(int, Boundary)> GetMatches(Regex regex, TextContainer tc, Clause clause, PatternScope[] scopes,
Boundary? boundary)
{
foreach (var match in regex.Matches(boundary is null ? tc.FullContent : tc.GetBoundaryText(boundary)))
if (match is Match m)
{
try
Boundary translatedBoundary = new()
{
RegexCache.TryAdd((built, regexOptions), new Regex(built, regexOptions));
}
catch (ArgumentException)
{
_logger.LogWarning("Provided regex {Regex} was not valid and could not be used", built);
RegexCache.TryAdd((built, regexOptions), null);
}
Length = m.Length,
Index = m.Index + (boundary?.Index ?? 0)
};
//regex patterns will be indexed off data while string patterns result in N clauses
var patternIndex = Convert.ToInt32(clause.Label);
// Should return only scoped matches
if (tc.ScopeMatch(scopes, translatedBoundary)) yield return (patternIndex, translatedBoundary);
}
return RegexCache[(built, regexOptions)];
}
}
/// <summary>
/// Converts a strings to a compiled regex.
/// Uses an internal cache.
/// </summary>
/// <param name="built">The regex to build</param>
/// <param name="regexOptions">The options to use.</param>
/// <returns>The built Regex</returns>
private Regex? StringToRegex(string built, RegexOptions regexOptions)
{
if (!RegexCache.ContainsKey((built, regexOptions)))
try
{
RegexCache.TryAdd((built, regexOptions), new Regex(built, regexOptions));
}
catch (ArgumentException)
{
_logger.LogWarning("Provided regex {Regex} was not valid and could not be used", built);
RegexCache.TryAdd((built, regexOptions), null);
}
return RegexCache[(built, regexOptions)];
}
}

Просмотреть файл

@ -2,25 +2,25 @@
using Microsoft.CST.OAT;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
public class OatSubstringIndexClause : Clause
{
public class OatSubstringIndexClause : Clause
public OatSubstringIndexClause(PatternScope[] scopes, string? field = null, bool useWordBoundaries = false,
string[]? xPaths = null, string[]? jsonPaths = null) : base(Operation.Custom, field)
{
public OatSubstringIndexClause(PatternScope[] scopes, string? field = null, bool useWordBoundaries = false, string[]? xPaths = null, string[]? jsonPaths = null) : base(Operation.Custom, field)
{
Scopes = scopes;
CustomOperation = "SubstringIndex";
UseWordBoundaries = useWordBoundaries;
XPaths = xPaths;
JsonPaths = jsonPaths;
}
public string[]? JsonPaths { get; }
public string[]? XPaths { get; }
public PatternScope[] Scopes { get; }
public bool UseWordBoundaries {get;}
Scopes = scopes;
CustomOperation = "SubstringIndex";
UseWordBoundaries = useWordBoundaries;
XPaths = xPaths;
JsonPaths = jsonPaths;
}
public string[]? JsonPaths { get; }
public string[]? XPaths { get; }
public PatternScope[] Scopes { get; }
public bool UseWordBoundaries { get; }
}

Просмотреть файл

@ -8,146 +8,148 @@ using Microsoft.CST.OAT.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
/// <summary>
/// The Custom Operation to enable identification of pattern index in result used by Application Inspector to report
/// why a given
/// result was matched and to retrieve other pattern level meta-data
/// </summary>
public class OatSubstringIndexOperation : OatOperation
{
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// The Custom Operation to enable identification of pattern index in result used by Application Inspector to report why a given
/// result was matched and to retrieve other pattern level meta-data
/// Create an OatOperation given an analyzer
/// </summary>
public class OatSubstringIndexOperation : OatOperation
/// <param name="analyzer">The analyzer context to work with</param>
/// <param name="loggerFactory">LoggerFactory to use</param>
public OatSubstringIndexOperation(Analyzer analyzer, ILoggerFactory? loggerFactory = null) : base(Operation.Custom,
analyzer)
{
private readonly ILoggerFactory _loggerFactory;
_loggerFactory = loggerFactory ?? new NullLoggerFactory();
CustomOperation = "SubstringIndex";
OperationDelegate = SubstringIndexOperationDelegate;
ValidationDelegate = SubstringIndexValidationDelegate;
}
/// <summary>
/// Create an OatOperation given an analyzer
/// </summary>
/// <param name="analyzer">The analyzer context to work with</param>
/// <param name="loggerFactory">LoggerFactory to use</param>
public OatSubstringIndexOperation(Analyzer analyzer, ILoggerFactory? loggerFactory = null) : base(Operation.Custom, analyzer)
{
_loggerFactory = loggerFactory ?? new NullLoggerFactory();
CustomOperation = "SubstringIndex";
OperationDelegate = SubstringIndexOperationDelegate;
ValidationDelegate = SubstringIndexValidationDelegate;
}
public static IEnumerable<Violation> SubstringIndexValidationDelegate(CST.OAT.Rule rule, Clause clause)
{
if (clause.Data?.Count is null or 0)
{
yield return new Violation(string.Format(Strings.Get("Err_ClauseNoData"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)), rule, clause);
}
if (clause.DictData?.Count is not null && clause.DictData.Count > 0)
{
yield return new Violation(string.Format(Strings.Get("Err_ClauseDictDataUnexpected"), rule.Name, clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture), clause.Operation.ToString()), rule, clause);
}
}
public static IEnumerable<Violation> SubstringIndexValidationDelegate(CST.OAT.Rule rule, Clause clause)
{
if (clause.Data?.Count is null or 0)
yield return new Violation(
string.Format(Strings.Get("Err_ClauseNoData"), rule.Name,
clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)), rule, clause);
if (clause.DictData?.Count is not null && clause.DictData.Count > 0)
yield return new Violation(
string.Format(Strings.Get("Err_ClauseDictDataUnexpected"), rule.Name,
clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture),
clause.Operation.ToString()), rule, clause);
}
/// <summary>
/// Returns results with pattern index and Boundary as a tuple to enable retrieval of Rule pattern level meta-data like Confidence and report the
/// pattern that was responsible for the match
/// </summary>
/// <param name="clause"></param>
/// <param name="state1"></param>
/// <param name="state2"></param>
/// <param name="captures"></param>
/// <returns></returns>
private OperationResult SubstringIndexOperationDelegate(Clause clause, object? state1, object? state2, IEnumerable<ClauseCapture>? captures)
{
var comparisonType = clause.Arguments.Contains("i") ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
if (state1 is TextContainer tc && clause is OatSubstringIndexClause src)
/// <summary>
/// Returns results with pattern index and Boundary as a tuple to enable retrieval of Rule pattern level meta-data like
/// Confidence and report the
/// pattern that was responsible for the match
/// </summary>
/// <param name="clause"></param>
/// <param name="state1"></param>
/// <param name="state2"></param>
/// <param name="captures"></param>
/// <returns></returns>
private OperationResult SubstringIndexOperationDelegate(Clause clause, object? state1, object? state2,
IEnumerable<ClauseCapture>? captures)
{
var comparisonType = clause.Arguments.Contains("i")
? StringComparison.InvariantCultureIgnoreCase
: StringComparison.InvariantCulture;
if (state1 is TextContainer tc && clause is OatSubstringIndexClause src)
if (clause.Data is { Count: > 0 } stringList)
{
if (clause.Data is { Count: > 0 } stringList)
var outmatches = new List<(int, Boundary)>(); //tuple results i.e. pattern index and where
for (var i = 0; i < stringList.Count; i++)
{
var outmatches = new List<(int, Boundary)>();//tuple results i.e. pattern index and where
for (int i = 0; i < stringList.Count; i++)
{
if (src.XPaths is not null)
if (src.XPaths is not null)
foreach (var xmlPath in src.XPaths)
{
foreach (var xmlPath in src.XPaths)
var targets = tc.GetStringFromXPath(xmlPath);
foreach (var target in targets)
{
var targets = tc.GetStringFromXPath(xmlPath);
foreach (var target in targets)
var matches = GetMatches(target.Item1, stringList[i], comparisonType, tc, src);
foreach (var match in matches)
{
var matches = GetMatches(target.Item1, stringList[i], comparisonType, tc, src);
foreach (var match in matches)
{
match.Index += target.Item2.Index;
outmatches.Add((i,match));
}
match.Index += target.Item2.Index;
outmatches.Add((i, match));
}
}
}
if (src.JsonPaths is not null)
if (src.JsonPaths is not null)
foreach (var jsonPath in src.JsonPaths)
{
foreach (var jsonPath in src.JsonPaths)
var targets = tc.GetStringFromJsonPath(jsonPath);
foreach (var target in targets)
{
var targets = tc.GetStringFromJsonPath(jsonPath);
foreach (var target in targets)
var matches = GetMatches(target.Item1, stringList[i], comparisonType, tc, src);
foreach (var match in matches)
{
var matches = GetMatches(target.Item1, stringList[i], comparisonType, tc, src);
foreach (var match in matches)
{
match.Index += target.Item2.Index;
outmatches.Add((i,match));
}
match.Index += target.Item2.Index;
outmatches.Add((i, match));
}
}
}
if (src.JsonPaths is null && src.XPaths is null)
{
// If state 2 is a boundary, restrict the text provided to check to match the boundary
if (state2 is Boundary boundary)
{
var matches = GetMatches(tc.GetBoundaryText(boundary), stringList[i], comparisonType, tc, src);
outmatches.AddRange(matches.Select(x => (i, x))); }
else
{
var matches = GetMatches(tc.FullContent, stringList[i], comparisonType, tc, src);
outmatches.AddRange(matches.Select(x => (i, x))); }
if (src.JsonPaths is null && src.XPaths is null)
{
// If state 2 is a boundary, restrict the text provided to check to match the boundary
if (state2 is Boundary boundary)
{
var matches = GetMatches(tc.GetBoundaryText(boundary), stringList[i], comparisonType, tc,
src);
outmatches.AddRange(matches.Select(x => (i, x)));
}
else
{
var matches = GetMatches(tc.FullContent, stringList[i], comparisonType, tc, src);
outmatches.AddRange(matches.Select(x => (i, x)));
}
}
var result = src.Invert ? outmatches.Count == 0 : outmatches.Count > 0;
return new OperationResult(result, result && src.Capture ? new TypedClauseCapture<List<(int, Boundary)>>(clause, outmatches, state1) : null);
}
}
return new OperationResult(false, null);
}
private static IEnumerable<Boundary> GetMatches(string target, string query, StringComparison comparisonType, TextContainer tc, OatSubstringIndexClause src)
var result = src.Invert ? outmatches.Count == 0 : outmatches.Count > 0;
return new OperationResult(result,
result && src.Capture
? new TypedClauseCapture<List<(int, Boundary)>>(clause, outmatches, state1)
: null);
}
return new OperationResult(false);
}
private static IEnumerable<Boundary> GetMatches(string target, string query, StringComparison comparisonType,
TextContainer tc, OatSubstringIndexClause src)
{
var idx = target.IndexOf(query, comparisonType);
while (idx != -1)
{
var idx = target.IndexOf(query, comparisonType);
while (idx != -1)
var skip = false;
if (src.UseWordBoundaries)
{
bool skip = false;
if (src.UseWordBoundaries)
{
if (idx > 0 && char.IsLetterOrDigit(target[idx - 1]))
{
skip = true;
}
if (idx + query.Length < target.Length && char.IsLetterOrDigit(target[idx + query.Length]))
{
skip = true;
}
}
if (!skip)
{
Boundary newBoundary = new()
{
Length = query.Length,
Index = idx
};
if (tc.ScopeMatch(src.Scopes, newBoundary))
{
yield return newBoundary;
}
}
idx = target.IndexOf(query, idx + query.Length, comparisonType);
if (idx > 0 && char.IsLetterOrDigit(target[idx - 1])) skip = true;
if (idx + query.Length < target.Length && char.IsLetterOrDigit(target[idx + query.Length])) skip = true;
}
if (!skip)
{
Boundary newBoundary = new()
{
Length = query.Length,
Index = idx
};
if (tc.ScopeMatch(src.Scopes, newBoundary)) yield return newBoundary;
}
idx = target.IndexOf(query, idx + query.Length, comparisonType);
}
}
}

Просмотреть файл

@ -2,24 +2,23 @@
using Microsoft.CST.OAT;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
{
public class WithinClause : Clause
{
public WithinClause(Clause subClause, string? field = null) : base(Operation.Custom, field)
{
SubClause = subClause;
CustomOperation = "Within";
}
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
public int After { get; set; }
public int Before { get; set; }
public bool OnlyBefore { get; set; }
public bool OnlyAfter { get; set; }
public bool SameFile { get; set; }
public bool FindingOnly { get; set; }
public bool SameLineOnly { get; set; }
public bool FindingRegion { get; set; }
public Clause SubClause { get; }
public class WithinClause : Clause
{
public WithinClause(Clause subClause, string? field = null) : base(Operation.Custom, field)
{
SubClause = subClause;
CustomOperation = "Within";
}
public int After { get; set; }
public int Before { get; set; }
public bool OnlyBefore { get; set; }
public bool OnlyAfter { get; set; }
public bool SameFile { get; set; }
public bool FindingOnly { get; set; }
public bool SameLineOnly { get; set; }
public bool FindingRegion { get; set; }
public Clause SubClause { get; }
}

Просмотреть файл

@ -7,211 +7,192 @@ using Microsoft.CST.OAT.Operations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions
namespace Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
public class WithinOperation : OatOperation
{
public class WithinOperation : OatOperation
private readonly Analyzer _analyzer;
private readonly ILoggerFactory _loggerFactory;
public WithinOperation(Analyzer analyzer, ILoggerFactory? loggerFactory = null) : base(Operation.Custom, analyzer)
{
public WithinOperation(Analyzer analyzer, ILoggerFactory? loggerFactory = null) : base(Operation.Custom, analyzer)
{
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
_analyzer = analyzer;
CustomOperation = "Within";
OperationDelegate = WithinOperationDelegate;
ValidationDelegate = WithinValidationDelegate;
}
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
_analyzer = analyzer;
CustomOperation = "Within";
OperationDelegate = WithinOperationDelegate;
ValidationDelegate = WithinValidationDelegate;
}
public OperationResult WithinOperationDelegate(Clause c, object? state1, object? _, IEnumerable<ClauseCapture>? captures)
public OperationResult WithinOperationDelegate(Clause c, object? state1, object? _,
IEnumerable<ClauseCapture>? captures)
{
if (c is WithinClause wc && state1 is TextContainer tc)
{
if (c is WithinClause wc && state1 is TextContainer tc)
var passed =
new List<(int, Boundary)>();
var failed =
new List<(int, Boundary)>();
foreach (var capture in captures ?? Array.Empty<ClauseCapture>())
{
List<(int, Boundary)> passed =
new List<(int, Boundary)>();
List<(int, Boundary)> failed =
new List<(int, Boundary)>();
foreach (var capture in captures ?? Array.Empty<ClauseCapture>())
{
if (capture is TypedClauseCapture<List<(int, Boundary)>> tcc)
if (capture is TypedClauseCapture<List<(int, Boundary)>> tcc)
foreach ((var clauseNum, var boundary) in tcc.Result)
{
foreach ((int clauseNum, Boundary boundary) in tcc.Result)
var boundaryToCheck = GetBoundaryToCheck();
if (boundaryToCheck is not null)
{
var boundaryToCheck = GetBoundaryToCheck();
if (boundaryToCheck is not null)
var operationResult = ProcessLambda(boundaryToCheck);
if (operationResult.Result)
passed.Add((clauseNum, boundary));
else
failed.Add((clauseNum, boundary));
}
Boundary? GetBoundaryToCheck()
{
if (wc.FindingOnly) return boundary;
if (wc.SameLineOnly)
{
var operationResult = ProcessLambda(boundaryToCheck);
if (operationResult.Result)
var startInner = tc.LineStarts[tc.GetLocation(boundary.Index).Line];
var endInner = tc.LineEnds[tc.GetLocation(startInner + (boundary.Length - 1)).Line];
return new Boundary
{
passed.Add((clauseNum, boundary));
}
else
{
failed.Add((clauseNum, boundary));
}
Index = startInner,
Length = endInner - startInner + 1
};
}
Boundary? GetBoundaryToCheck()
if (wc.FindingRegion)
{
if (wc.FindingOnly)
var startLine = tc.GetLocation(boundary.Index).Line;
// Before is already a negative number
var startInner = tc.LineStarts[Math.Max(1, startLine + wc.Before)];
var endInner = tc.LineEnds[Math.Min(tc.LineEnds.Count - 1, startLine + wc.After)];
return new Boundary
{
return boundary;
}
if (wc.SameLineOnly)
{
var startInner = tc.LineStarts[tc.GetLocation(boundary.Index).Line];
var endInner = tc.LineEnds[tc.GetLocation(startInner + (boundary.Length - 1)).Line];
return new Boundary()
{
Index = startInner,
Length = (endInner - startInner) + 1
};
}
if (wc.FindingRegion)
{
var startLine = tc.GetLocation(boundary.Index).Line;
// Before is already a negative number
var startInner = tc.LineStarts[Math.Max(1, startLine + wc.Before)];
var endInner = tc.LineEnds[Math.Min(tc.LineEnds.Count - 1, startLine + wc.After)];
return new Boundary()
{
Index = startInner,
Length = (endInner - startInner) + 1
};
}
if (wc.SameFile)
{
var startInner = tc.LineStarts[0];
var endInner = tc.LineEnds[^1];
return new Boundary()
{
Index = startInner,
Length = (endInner - startInner) + 1
};
}
if (wc.OnlyBefore)
{
var startInner = tc.LineStarts[0];
var endInner = boundary.Index;
return new Boundary()
{
Index = startInner,
Length = (endInner - startInner) + 1
};
}
if (wc.OnlyAfter)
{
var startInner = boundary.Index + boundary.Length;
var endInner = tc.LineEnds[^1];
return new Boundary()
{
Index = startInner,
Length = (endInner - startInner) + 1
};
}
return null;
Index = startInner,
Length = endInner - startInner + 1
};
}
if (wc.SameFile)
{
var startInner = tc.LineStarts[0];
var endInner = tc.LineEnds[^1];
return new Boundary
{
Index = startInner,
Length = endInner - startInner + 1
};
}
if (wc.OnlyBefore)
{
var startInner = tc.LineStarts[0];
var endInner = boundary.Index;
return new Boundary
{
Index = startInner,
Length = endInner - startInner + 1
};
}
if (wc.OnlyAfter)
{
var startInner = boundary.Index + boundary.Length;
var endInner = tc.LineEnds[^1];
return new Boundary
{
Index = startInner,
Length = endInner - startInner + 1
};
}
return null;
}
}
var passedOrFailed = wc.Invert ? failed : passed;
return new OperationResult(passedOrFailed.Any(), passedOrFailed.Any() ? new TypedClauseCapture<List<(int, Boundary)>>(wc, passedOrFailed.ToList()) : null);
}
OperationResult ProcessLambda(Boundary target)
{
return _analyzer.GetClauseCapture(wc.SubClause, tc, target, captures);
}
var passedOrFailed = wc.Invert ? failed : passed;
return new OperationResult(passedOrFailed.Any(),
passedOrFailed.Any()
? new TypedClauseCapture<List<(int, Boundary)>>(wc, passedOrFailed.ToList())
: null);
}
return new OperationResult(false);
}
public IEnumerable<Violation> WithinValidationDelegate(CST.OAT.Rule rule, Clause clause)
{
if (clause is WithinClause wc)
OperationResult ProcessLambda(Boundary target)
{
if (new bool[] {wc.FindingOnly, wc.SameLineOnly, wc.FindingRegion, wc.OnlyAfter, wc.OnlyBefore, wc.SameFile}.Count(x => x) != 1)
{
yield return new Violation($"Exactly one of: FindingOnly, SameLineOnly, OnlyAfter, OnlyBefore, SameFile or FindingRegion must be set", rule, clause);
}
return _analyzer.GetClauseCapture(wc.SubClause, tc, target, captures);
}
}
if (wc.FindingRegion)
{
if (wc.Before == 0 && wc.After == 0)
{
yield return new Violation(
$"Both parameters for finding-region may not be 0. Use same-line to only analyze the same line.",
rule, clause);
}
return new OperationResult(false);
}
if (wc.Before > 0)
{
yield return new Violation(
$"The first parameter for finding region, representing number of lines before, must be 0 or negative",
rule, clause);
}
public IEnumerable<Violation> WithinValidationDelegate(CST.OAT.Rule rule, Clause clause)
{
if (clause is WithinClause wc)
{
if (new[] { wc.FindingOnly, wc.SameLineOnly, wc.FindingRegion, wc.OnlyAfter, wc.OnlyBefore, wc.SameFile }
.Count(x => x) !=
1)
yield return new Violation(
"Exactly one of: FindingOnly, SameLineOnly, OnlyAfter, OnlyBefore, SameFile or FindingRegion must be set",
rule, clause);
if (wc.After < 0)
{
yield return new Violation(
$"The second parameter for finding region, representing number of lines after, must be 0 or positive",
rule, clause);
}
}
if (wc.Data.Any())
{
yield return new Violation($"Don't provide data directly. Instead use SubClause.", rule, clause);
}
var subOp = _analyzer
.GetOperation(wc.SubClause.Key.Operation, wc.SubClause.Key.CustomOperation);
if (wc.FindingRegion)
{
if (wc.Before == 0 && wc.After == 0)
yield return new Violation(
"Both parameters for finding-region may not be 0. Use same-line to only analyze the same line.",
rule, clause);
if (subOp is null)
{
yield return new Violation($"SubClause in Rule {rule.Name} Clause {clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)} is of type '{wc.SubClause.Key.Operation},{wc.SubClause.Key.CustomOperation}' is not present in the analyzer.", rule, clause);
}
else
{
foreach (var violation in subOp.ValidationDelegate.Invoke(rule, wc.SubClause))
{
yield return violation;
}
if (wc.Before > 0)
yield return new Violation(
"The first parameter for finding region, representing number of lines before, must be 0 or negative",
rule, clause);
if (wc.SubClause is OatRegexWithIndexClause oatRegexWithIndexClause)
{
if ((oatRegexWithIndexClause.JsonPaths?.Any() ?? false) ||
(oatRegexWithIndexClause.XPaths?.Any() ?? false))
{
if (wc.FindingOnly || wc.SameLineOnly || wc.FindingRegion || wc.OnlyAfter || wc.OnlyBefore)
{
yield return new Violation($"When providing JSONPaths or XPaths must use same-file region.", rule, clause);
}
}
}
if (wc.SubClause is OatSubstringIndexClause oatSubstringIndexClause)
{
if ((oatSubstringIndexClause.JsonPaths?.Any() ?? false) ||
(oatSubstringIndexClause.XPaths?.Any() ?? false))
{
if (wc.FindingOnly || wc.SameLineOnly || wc.FindingRegion || wc.OnlyAfter || wc.OnlyBefore)
{
yield return new Violation($"When providing JSONPaths or XPaths must use same-file region.", rule, clause);
}
}
}
}
if (wc.After < 0)
yield return new Violation(
"The second parameter for finding region, representing number of lines after, must be 0 or positive",
rule, clause);
}
if (wc.Data.Any())
yield return new Violation("Don't provide data directly. Instead use SubClause.", rule, clause);
var subOp = _analyzer
.GetOperation(wc.SubClause.Key.Operation, wc.SubClause.Key.CustomOperation);
if (subOp is null)
{
yield return new Violation(
$"SubClause in Rule {rule.Name} Clause {clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)} is of type '{wc.SubClause.Key.Operation},{wc.SubClause.Key.CustomOperation}' is not present in the analyzer.",
rule, clause);
}
else
{
yield return new Violation($"Rule {rule.Name} clause {clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)} is not a WithinClause", rule, clause);
foreach (var violation in subOp.ValidationDelegate.Invoke(rule, wc.SubClause)) yield return violation;
if (wc.SubClause is OatRegexWithIndexClause oatRegexWithIndexClause)
if ((oatRegexWithIndexClause.JsonPaths?.Any() ?? false) ||
(oatRegexWithIndexClause.XPaths?.Any() ?? false))
if (wc.FindingOnly || wc.SameLineOnly || wc.FindingRegion || wc.OnlyAfter || wc.OnlyBefore)
yield return new Violation("When providing JSONPaths or XPaths must use same-file region.",
rule, clause);
if (wc.SubClause is OatSubstringIndexClause oatSubstringIndexClause)
if ((oatSubstringIndexClause.JsonPaths?.Any() ?? false) ||
(oatSubstringIndexClause.XPaths?.Any() ?? false))
if (wc.FindingOnly || wc.SameLineOnly || wc.FindingRegion || wc.OnlyAfter || wc.OnlyBefore)
yield return new Violation("When providing JSONPaths or XPaths must use same-file region.",
rule, clause);
}
}
private readonly ILoggerFactory _loggerFactory;
private readonly Analyzer _analyzer;
else
{
yield return new Violation(
$"Rule {rule.Name} clause {clause.Label ?? rule.Clauses.IndexOf(clause).ToString(CultureInfo.InvariantCulture)} is not a WithinClause",
rule, clause);
}
}
}

Просмотреть файл

@ -3,14 +3,13 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
[JsonConverter(typeof(StringEnumConverter))]
public enum PatternScope
{
[JsonConverter(typeof(StringEnumConverter))]
public enum PatternScope
{
All,
Code,
Comment,
Html
}
All,
Code,
Comment,
Html
}

Просмотреть файл

@ -3,17 +3,16 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Pattern Type for search pattern
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum PatternType
{
/// <summary>
/// Pattern Type for search pattern
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum PatternType
{
Regex,
RegexWord,
String,
Substring
}
Regex,
RegexWord,
String,
Substring
}

Просмотреть файл

@ -1,217 +1,327 @@
[
{
"name": "c",
"extensions": [ ".c", ".h" ],
"extensions": [
".c",
".h"
],
"type": "code"
},
{
"name": "cpp",
"extensions": [ ".cpp", ".hpp", ".cxx" ],
"extensions": [
".cpp",
".hpp",
".cxx"
],
"type": "code"
},
{
"name": "csharp",
"extensions": [ ".cs" ],
"extensions": [
".cs"
],
"type": "code"
},
{
"name": "fsharp",
"extensions": [ ".fs" ],
"extensions": [
".fs"
],
"type": "code"
},
{
"name": "vb",
"extensions": [ ".vb" ],
"extensions": [
".vb"
],
"type": "code"
},
{
"name": "python",
"extensions": [ ".py", ".py3", ".pyw" ],
"extensions": [
".py",
".py3",
".pyw"
],
"type": "code"
},
{
"name": "html",
"extensions": [ ".html", ".htm", ".cshtml", ".tmpl" ],
"extensions": [
".html",
".htm",
".cshtml",
".tmpl"
],
"type": "code"
},
{
"name": "javascript",
"extensions": [ ".js" ],
"extensions": [
".js"
],
"type": "code"
},
{
"name": "javascriptreact",
"extensions": [ ".jsx" ],
"extensions": [
".jsx"
],
"type": "code"
},
{
"name": "typescript",
"extensions": [ ".ts" ],
"extensions": [
".ts"
],
"type": "code"
},
{
"name": "typescriptreact",
"extensions": [ ".tsx" ],
"extensions": [
".tsx"
],
"type": "code"
},
{
"name": "coffeescript",
"extensions": [ ".coffee" ],
"extensions": [
".coffee"
],
"type": "code"
},
{
"name": "dart",
"extensions": [ ".dart" ],
"extensions": [
".dart"
],
"type": "code"
},
{
"name": "java",
"extensions": [ ".java" ],
"extensions": [
".java"
],
"type": "code"
},
{
"name": "kotlin",
"extensions": [ ".kts" ],
"extensions": [
".kts"
],
"type": "code"
},
{
"name": "scala",
"extensions": [ ".scala" ],
"extensions": [
".scala"
],
"type": "code"
},
{
"name": "objective-c",
"extensions": [ ".m", ".mm" ],
"extensions": [
".m",
".mm"
],
"type": "code"
},
{
"name": "swift",
"extensions": [ ".swift" ],
"extensions": [
".swift"
],
"type": "code"
},
{
"name": "perl",
"extensions": [ ".pl", ".pm", ".t", ".pod" ],
"extensions": [
".pl",
".pm",
".t",
".pod"
],
"type": "code"
},
{
"name": "perl6",
"extensions": [ ".pl6", ".p6", ".pm6" ],
"extensions": [
".pl6",
".p6",
".pm6"
],
"type": "code"
},
{
"name": "ruby",
"extensions": [ ".rb" ],
"extensions": [
".rb"
],
"type": "code"
},
{
"name": "lua",
"extensions": [ ".lua" ],
"extensions": [
".lua"
],
"type": "code"
},
{
"name": "groovy",
"extensions": [ ".groovy" ],
"extensions": [
".groovy"
],
"type": "code"
},
{
"name": "go",
"extensions": [ ".go" ],
"extensions": [
".go"
],
"type": "code"
},
{
"name": "rust",
"extensions": [ ".rs" ],
"extensions": [
".rs"
],
"type": "code"
},
{
"name": "jade",
"extensions": [ ".jade" ],
"extensions": [
".jade"
],
"type": "code"
},
{
"name": "clojure",
"extensions": [ ".clj", ".cljs", ".cljc", ".edn" ],
"extensions": [
".clj",
".cljs",
".cljc",
".edn"
],
"type": "code"
},
{
"name": "r",
"extensions": [ ".r" ],
"extensions": [
".r"
],
"type": "code"
},
{
"name": "php",
"extensions": [ ".php" ],
"extensions": [
".php"
],
"type": "code"
},
{
"name": "powershell",
"extensions": [ ".ps1", ".psm1", ".psd1" ],
"extensions": [
".ps1",
".psm1",
".psd1"
],
"type": "code"
},
{
"name": "shellscript",
"extensions": [ ".sh" ],
"extensions": [
".sh"
],
"type": "code"
},
{
"name": "wincmdscript",
"extensions": [ ".bat" ],
"extensions": [
".bat"
],
"type": "code"
},
{
"name": "sql",
"extensions": [ ".sql" ],
"extensions": [
".sql"
],
"type": "code"
},
{
"name": "yaml",
"extensions": [ ".yaml", ".yml" ],
"extensions": [
".yaml",
".yml"
],
"type": "build"
},
{
"name": "package.json",
"file-names": ["package.json"],
"file-names": [
"package.json"
],
"type": "build"
},
{
"name": "nugetpkg",
"file-names": [ "packages.config" ],
"file-names": [
"packages.config"
],
"type": "build"
},
{
"name": "VSSolution",
"extensions": [ ".sln" ],
"extensions": [
".sln"
],
"type": "build"
},
{
"name": "VSProject",
"extensions": [ ".vcxproj", ".ccproj", ".csproj", ".njsproj", ".vbproj" ],
"extensions": [
".vcxproj",
".ccproj",
".csproj",
".njsproj",
".vbproj"
],
"type": "build"
},
{
"name": "pom.xml",
"file-names": [ "pom.xml" ],
"file-names": [
"pom.xml"
],
"type": "build"
},
{
"name": "build.xml",
"file-names": [ "build.xml" ],
"file-names": [
"build.xml"
],
"type": "build"
},
{
"name": "build.gradle",
"file-names": [ "build.gradle" ],
"file-names": [
"build.gradle"
],
"type": "build"
},
{
"name": "build.make.xml",
"file-names": [ "build.make.xml" ],
"file-names": [
"build.make.xml"
],
"type": "build"
},
{
"name": "jenkins",
"extensions": [ ".hpi" ],
"extensions": [
".hpi"
],
"type": "build"
},
{
@ -221,7 +331,9 @@
},
{
"name": "sbt",
"extensions": [ ".sbt" ],
"extensions": [
".sbt"
],
"type": "build"
},
{
@ -231,32 +343,45 @@
},
{
"name": "typescript-config",
"file-names": [ "tsconfig.json", "typings.json" ],
"file-names": [
"tsconfig.json",
"typings.json"
],
"type": "build"
},
{
"name": "json",
"extensions": [ ".json" ],
"extensions": [
".json"
],
"type": "build"
},
{
"name": "terraform",
"extensions": [ ".tf" ],
"extensions": [
".tf"
],
"type": "code"
},
{
"name": ".config",
"extensions": [ ".config" ],
"extensions": [
".config"
],
"type": "build"
},
{
"name": "Package.appxmanifest",
"extensions": [ ".appxmanifest" ],
"extensions": [
".appxmanifest"
],
"type": "code"
},
{
"name": "XML",
"extensions": [ ".xml" ],
"extensions": [
".xml"
],
"type": "build"
}
]

Просмотреть файл

@ -1,106 +1,101 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Class to hold the Rule
/// </summary>
public class Rule
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
private IEnumerable<Regex> _compiled = Array.Empty<Regex>();
private string[]? _fileRegexes;
private bool _updateCompiledFileRegex;
/// <summary>
/// Class to hold the Rule
/// Name of the source where the rule definition came from.
/// Typically file, database or other storage.
/// </summary>
public class Rule
[JsonIgnore]
public string? Source { get; set; }
/// <summary>
/// Optional tag assigned to the rule during runtime
/// </summary>
[JsonIgnore]
public string? RuntimeTag { get; set; }
/// <summary>
/// Runtime flag to disable the rule
/// </summary>
[JsonIgnore]
public bool Disabled { get; set; }
[JsonProperty(PropertyName = "name")] public string Name { get; set; } = "";
[JsonProperty(PropertyName = "id")] public string Id { get; set; } = "";
[JsonProperty(PropertyName = "description")]
public string? Description { get; set; } = "";
[JsonProperty(PropertyName = "does_not_apply_to")]
public List<string>? DoesNotApplyTo { get; set; }
[JsonProperty(PropertyName = "applies_to")]
public string[]? AppliesTo { get; set; }
[JsonProperty(PropertyName = "applies_to_file_regex")]
public string[]? FileRegexes
{
/// <summary>
/// Name of the source where the rule definition came from.
/// Typically file, database or other storage.
/// </summary>
[JsonIgnore]
public string? Source { get; set; }
/// <summary>
/// Optional tag assigned to the rule during runtime
/// </summary>
[JsonIgnore]
public string? RuntimeTag { get; set; }
/// <summary>
/// Runtime flag to disable the rule
/// </summary>
[JsonIgnore]
public bool Disabled { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; } = "";
[JsonProperty(PropertyName = "id")]
public string Id { get; set; } = "";
[JsonProperty(PropertyName = "description")]
public string? Description { get; set; } = "";
[JsonProperty(PropertyName = "does_not_apply_to")]
public List<string>? DoesNotApplyTo { get; set; }
[JsonProperty(PropertyName = "applies_to")]
public string[]? AppliesTo { get; set; }
[JsonProperty(PropertyName = "applies_to_file_regex")]
public string[]? FileRegexes
get => _fileRegexes;
set
{
get => _fileRegexes;
set
{
_fileRegexes = value;
_updateCompiledFileRegex = true;
}
_fileRegexes = value;
_updateCompiledFileRegex = true;
}
private string[]? _fileRegexes;
[JsonIgnore]
public IEnumerable<Regex> CompiledFileRegexes
{
get
{
if (_updateCompiledFileRegex)
{
_compiled = FileRegexes?.Select(x => new Regex(x, RegexOptions.Compiled)) ?? Array.Empty<Regex>();
_updateCompiledFileRegex = false;
}
return _compiled;
}
}
private IEnumerable<Regex> _compiled = Array.Empty<Regex>();
private bool _updateCompiledFileRegex = false;
[JsonProperty(PropertyName = "tags")]
public string[]? Tags { get; set; }
[JsonProperty(PropertyName = "severity")]
[JsonConverter(typeof(StringEnumConverter))]
public Severity Severity { get; set; } = Severity.Moderate;
[JsonProperty(PropertyName = "overrides")]
public string[]? Overrides { get; set; }
[JsonProperty(PropertyName = "patterns")]
public SearchPattern[] Patterns { get; set; } = Array.Empty<SearchPattern>();
[JsonProperty(PropertyName = "conditions")]
public SearchCondition[]? Conditions { get; set; }
[JsonProperty(PropertyName = "must-match")]
public string[]? MustMatch { get; set; }
[JsonProperty(PropertyName = "must-not-match")]
public string[]? MustNotMatch { get; set; }
}
[JsonIgnore]
public IEnumerable<Regex> CompiledFileRegexes
{
get
{
if (_updateCompiledFileRegex)
{
_compiled = FileRegexes?.Select(x => new Regex(x, RegexOptions.Compiled)) ?? Array.Empty<Regex>();
_updateCompiledFileRegex = false;
}
return _compiled;
}
}
[JsonProperty(PropertyName = "tags")] public string[]? Tags { get; set; }
[JsonProperty(PropertyName = "severity")]
[JsonConverter(typeof(StringEnumConverter))]
public Severity Severity { get; set; } = Severity.Moderate;
[JsonProperty(PropertyName = "overrides")]
public string[]? Overrides { get; set; }
[JsonProperty(PropertyName = "patterns")]
public SearchPattern[] Patterns { get; set; } = Array.Empty<SearchPattern>();
[JsonProperty(PropertyName = "conditions")]
public SearchCondition[]? Conditions { get; set; }
[JsonProperty(PropertyName = "must-match")]
public string[]? MustMatch { get; set; }
[JsonProperty(PropertyName = "must-not-match")]
public string[]? MustNotMatch { get; set; }
}

Просмотреть файл

@ -1,535 +1,491 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
using Microsoft.CST.OAT;
using Microsoft.CST.RecursiveExtractor;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
[ExcludeFromCodeCoverage]
public class RuleProcessorOptions
{
using Microsoft.CST.OAT;
using Microsoft.CST.RecursiveExtractor;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
public bool Parallel { get; set; } = true;
[ExcludeFromCodeCoverage]
public class RuleProcessorOptions
public Confidence ConfidenceFilter { get; set; } =
Confidence.Unspecified | Confidence.Low | Confidence.Medium | Confidence.High;
public Severity SeverityFilter { get; set; } =
Severity.Critical | Severity.Important | Severity.Moderate | Severity.BestPractice;
public ILoggerFactory? LoggerFactory { get; set; }
public bool AllowAllTagsInBuildFiles { get; set; }
public bool EnableCache { get; set; } = true;
public Languages Languages { get; set; } = new();
}
/// <summary>
/// Heart of RulesEngine. Parses code applies rules
/// </summary>
public class RuleProcessor
{
private readonly Analyzer _analyzer;
private readonly ConcurrentDictionary<string, IEnumerable<ConvertedOatRule>> _fileRulesCache = new();
private readonly ConcurrentDictionary<string, IEnumerable<ConvertedOatRule>> _languageRulesCache = new();
private readonly Languages _languages;
private readonly ILogger<RuleProcessor> _logger;
private readonly RuleProcessorOptions _opts;
private readonly AbstractRuleSet _ruleset;
private readonly int MAX_TEXT_SAMPLE_LENGTH = 200; //char bytes
private IEnumerable<ConvertedOatRule>? _universalRulesCache;
/// <summary>
/// Creates instance of RuleProcessor
/// </summary>
public RuleProcessor(AbstractRuleSet rules, RuleProcessorOptions opts)
{
public RuleProcessorOptions()
{
}
_opts = opts;
_logger = opts.LoggerFactory?.CreateLogger<RuleProcessor>() ?? NullLogger<RuleProcessor>.Instance;
_languages = opts.Languages;
_ruleset = rules;
EnableCache = true;
public bool Parallel { get; set; } = true;
public Confidence ConfidenceFilter { get; set; } = Confidence.Unspecified | Confidence.Low | Confidence.Medium | Confidence.High;
public Severity SeverityFilter { get; set; } = Severity.Critical | Severity.Important | Severity.Moderate | Severity.BestPractice;
public ILoggerFactory? LoggerFactory { get; set; }
public bool AllowAllTagsInBuildFiles { get; set; }
public bool EnableCache { get; set; } = true;
public Languages Languages { get; set; } = new();
_analyzer = new ApplicationInspectorAnalyzer(_opts.LoggerFactory);
}
/// <summary>
/// Heart of RulesEngine. Parses code applies rules
/// Sets severity levels for analysis
/// </summary>
public class RuleProcessor
private Severity SeverityLevel => _opts.SeverityFilter;
/// <summary>
/// Enables caching of rules queries if multiple reuses per instance
/// </summary>
private bool EnableCache { get; }
private static string ExtractDependency(TextContainer? text, int startIndex, string? pattern, string? language)
{
private readonly int MAX_TEXT_SAMPLE_LENGTH = 200;//char bytes
if (text is null || string.IsNullOrEmpty(text.FullContent) || string.IsNullOrEmpty(language) ||
string.IsNullOrEmpty(pattern)) return string.Empty;
private readonly RuleProcessorOptions _opts;
private readonly ILogger<RuleProcessor> _logger;
private readonly Analyzer _analyzer;
private readonly AbstractRuleSet _ruleset;
private readonly Languages _languages;
private readonly ConcurrentDictionary<string, IEnumerable<ConvertedOatRule>> _fileRulesCache = new();
private readonly ConcurrentDictionary<string, IEnumerable<ConvertedOatRule>> _languageRulesCache = new();
private IEnumerable<ConvertedOatRule>? _universalRulesCache;
/// <summary>
/// Sets severity levels for analysis
/// </summary>
private Severity SeverityLevel => _opts.SeverityFilter;
/// <summary>
/// Enables caching of rules queries if multiple reuses per instance
/// </summary>
private bool EnableCache { get; }
/// <summary>
/// Creates instance of RuleProcessor
/// </summary>
public RuleProcessor(AbstractRuleSet rules, RuleProcessorOptions opts)
var rawResult = string.Empty;
var endIndex = text.FullContent.IndexOfAny(new[] { '\n', '\r' }, startIndex);
if (-1 != startIndex && -1 != endIndex)
{
_opts = opts;
_logger = opts.LoggerFactory?.CreateLogger<RuleProcessor>() ?? NullLogger<RuleProcessor>.Instance;
_languages = opts.Languages;
_ruleset = rules;
EnableCache = true;
rawResult = text.FullContent[startIndex..endIndex].Trim();
Regex regex = new(pattern);
var matches = regex.Matches(rawResult);
_analyzer = new ApplicationInspectorAnalyzer(_opts.LoggerFactory);
}
private static string ExtractDependency(TextContainer? text, int startIndex, string? pattern, string? language)
{
if (text is null || string.IsNullOrEmpty(text.FullContent) || string.IsNullOrEmpty(language) || string.IsNullOrEmpty(pattern))
{
return string.Empty;
}
string rawResult = string.Empty;
int endIndex = text.FullContent.IndexOfAny(new char[] { '\n', '\r' }, startIndex);
if (-1 != startIndex && -1 != endIndex)
{
rawResult = text.FullContent[startIndex..endIndex].Trim();
Regex regex = new(pattern);
MatchCollection matches = regex.Matches(rawResult);
//remove surrounding import or trailing comments
if (matches.Any())
//remove surrounding import or trailing comments
if (matches.Any())
foreach (Match? match in matches)
{
foreach (Match? match in matches)
if (match?.Groups.Count == 1) //handles cases like "using Newtonsoft.Json"
{
if (match?.Groups.Count == 1)//handles cases like "using Newtonsoft.Json"
{
string[] parseValues = match.Groups[0].Value.Split(' ');
if (parseValues.Length == 1)
{
rawResult = parseValues[0].Trim();
}
else if (parseValues.Length > 1)
{
rawResult = parseValues[1].Trim();
}
}
else if (match?.Groups.Count > 1)//handles cases like include <stdio.h>
{
rawResult = match.Groups[1].Value.Trim();
}
//else if > 2 too hard to match; do nothing
break;//only designed to expect one match per line i.e. not include value include value
string[] parseValues = match.Groups[0].Value.Split(' ');
if (parseValues.Length == 1)
rawResult = parseValues[0].Trim();
else if (parseValues.Length > 1) rawResult = parseValues[1].Trim();
}
else if (match?.Groups.Count > 1) //handles cases like include <stdio.h>
{
rawResult = match.Groups[1].Value.Trim();
}
//else if > 2 too hard to match; do nothing
break; //only designed to expect one match per line i.e. not include value include value
}
string finalResult = rawResult.Replace(";", "");
var finalResult = rawResult.Replace(";", "");
return System.Net.WebUtility.HtmlEncode(finalResult);
}
return rawResult;
return WebUtility.HtmlEncode(finalResult);
}
/// <summary>
/// Analyzes a file and returns a list of <see cref="MatchRecord"/>
/// </summary>
/// <param name="textContainer">TextContainer which holds the text to analyze</param>
/// <param name="fileEntry">FileEntry which has the name of the file being analyzed.</param>
/// <param name="languageInfo">The LanguageInfo for the file</param>
/// <param name="tagsToIgnore">Ignore rules that match tags that are only in the tags to ignore list</param>
/// <param name="numLinesContext">Number of lines of text to extract for the sample. Set to 0 to disable context gathering. Set to -1 to also disable sampling the match.</param>
/// <returns>A List of the matches against the Rules the processor is configured with.</returns>
public List<MatchRecord> AnalyzeFile(TextContainer textContainer, FileEntry fileEntry,
LanguageInfo languageInfo, IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
var rules = GetRulesForFile(languageInfo, fileEntry, tagsToIgnore);
List<MatchRecord> resultsList = new();
return rawResult;
}
var caps = _analyzer.GetCaptures(rules, textContainer);
foreach (var ruleCapture in caps)
/// <summary>
/// Analyzes a file and returns a list of <see cref="MatchRecord" />
/// </summary>
/// <param name="textContainer">TextContainer which holds the text to analyze</param>
/// <param name="fileEntry">FileEntry which has the name of the file being analyzed.</param>
/// <param name="languageInfo">The LanguageInfo for the file</param>
/// <param name="tagsToIgnore">Ignore rules that match tags that are only in the tags to ignore list</param>
/// <param name="numLinesContext">
/// Number of lines of text to extract for the sample. Set to 0 to disable context gathering.
/// Set to -1 to also disable sampling the match.
/// </param>
/// <returns>A List of the matches against the Rules the processor is configured with.</returns>
public List<MatchRecord> AnalyzeFile(TextContainer textContainer, FileEntry fileEntry,
LanguageInfo languageInfo, IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
var rules = GetRulesForFile(languageInfo, fileEntry, tagsToIgnore);
List<MatchRecord> resultsList = new();
var caps = _analyzer.GetCaptures(rules, textContainer);
foreach (var ruleCapture in caps)
{
var netCaptures = FilterCaptures(ruleCapture.Captures);
var oatRule = ruleCapture.Rule as ConvertedOatRule;
foreach (var match in netCaptures)
{
List<(int, Boundary)> netCaptures = FilterCaptures(ruleCapture.Captures);
var oatRule = ruleCapture.Rule as ConvertedOatRule;
foreach (var match in netCaptures)
var patternIndex = match.Item1;
var boundary = match.Item2;
//restrict adds from build files to tags with "metadata" only to avoid false feature positives that are not part of executable code
if (!_opts.AllowAllTagsInBuildFiles && languageInfo.Type == LanguageInfo.LangFileType.Build &&
(oatRule.AppInspectorRule.Tags?.Any(v => !v.Contains("Metadata")) ?? false)) continue;
if (!_opts.ConfidenceFilter.HasFlag(oatRule.AppInspectorRule.Patterns[patternIndex].Confidence))
continue;
var startLocation = textContainer.GetLocation(boundary.Index);
var endLocation = textContainer.GetLocation(boundary.Index + boundary.Length);
MatchRecord newMatch = new(oatRule.AppInspectorRule)
{
var patternIndex = match.Item1;
var boundary = match.Item2;
//restrict adds from build files to tags with "metadata" only to avoid false feature positives that are not part of executable code
if (!_opts.AllowAllTagsInBuildFiles && languageInfo.Type == LanguageInfo.LangFileType.Build && (oatRule.AppInspectorRule.Tags?.Any(v => !v.Contains("Metadata")) ?? false))
{
continue;
}
if (!_opts.ConfidenceFilter.HasFlag(oatRule.AppInspectorRule.Patterns[patternIndex].Confidence))
{
continue;
}
FileName = fileEntry.FullPath,
FullTextContainer = textContainer,
LanguageInfo = languageInfo,
Boundary = boundary,
StartLocationLine = startLocation.Line,
StartLocationColumn = startLocation.Column,
EndLocationLine =
endLocation.Line != 0 ? endLocation.Line : startLocation.Line + 1, //match is on last line
EndLocationColumn = endLocation.Column,
MatchingPattern = oatRule.AppInspectorRule.Patterns[patternIndex],
Excerpt = numLinesContext > 0
? ExtractExcerpt(textContainer, startLocation.Line, numLinesContext)
: string.Empty,
Sample = numLinesContext > -1
? ExtractTextSample(textContainer.FullContent, boundary.Index, boundary.Length)
: string.Empty
};
Location startLocation = textContainer.GetLocation(boundary.Index);
Location endLocation = textContainer.GetLocation(boundary.Index + boundary.Length);
MatchRecord newMatch = new(oatRule.AppInspectorRule)
{
FileName = fileEntry.FullPath,
FullTextContainer = textContainer,
LanguageInfo = languageInfo,
Boundary = boundary,
StartLocationLine = startLocation.Line,
StartLocationColumn = startLocation.Column,
EndLocationLine = endLocation.Line != 0 ? endLocation.Line : startLocation.Line + 1, //match is on last line
EndLocationColumn = endLocation.Column,
MatchingPattern = oatRule.AppInspectorRule.Patterns[patternIndex],
Excerpt = numLinesContext > 0 ? ExtractExcerpt(textContainer, startLocation.Line, numLinesContext) : string.Empty,
Sample = numLinesContext > -1 ? ExtractTextSample(textContainer.FullContent, boundary.Index, boundary.Length) : string.Empty
};
if (oatRule.AppInspectorRule.Tags?.Contains("Dependency.SourceInclude") ?? false)
newMatch.Sample = ExtractDependency(newMatch.FullTextContainer, newMatch.Boundary.Index,
newMatch.Pattern, newMatch.LanguageInfo.Name);
if (oatRule.AppInspectorRule.Tags?.Contains("Dependency.SourceInclude") ?? false)
{
newMatch.Sample = ExtractDependency(newMatch.FullTextContainer, newMatch.Boundary.Index, newMatch.Pattern, newMatch.LanguageInfo.Name);
}
resultsList.Add(newMatch);
}
// If a WithinClause capture is present, use only within captures, otherwise just flattens the list of results from the non-within clause.
List<(int, Boundary)> FilterCaptures(List<ClauseCapture> captures)
{
// If we had a WithinClause we only want the captures that passed the within filter.
if (captures.Any(x => x.Clause is WithinClause))
{
var onlyWithinCaptures = captures.Where(x => x.Clause is WithinClause).Cast<TypedClauseCapture<List<(int, Boundary)>>>().ToList();
var allCaptured = onlyWithinCaptures.SelectMany(x => x.Result);
ConcurrentDictionary<(int, Boundary), int> numberOfInstances = new();
// If there are multiple within clauses ensure that we only return matches which passed all clauses
// WithinClauses are always ANDed, but each contains all the captures that passed *that* clause.
// We need the captures that passed every clause.
foreach (var aCapture in allCaptured)
{
numberOfInstances.AddOrUpdate(aCapture, 1, (tuple, i) => i + 1);
}
return numberOfInstances.Where(x => x.Value == onlyWithinCaptures.Count).Select(x => x.Key).ToList();
}
else
{
var outList = new List<(int, Boundary)>();
foreach (var cap in captures)
{
if (cap is TypedClauseCapture<List<(int, Boundary)>> tcc)
{
outList.AddRange(tcc.Result);
}
}
return outList;
}
}
resultsList.Add(newMatch);
}
List<MatchRecord> removes = new();
foreach (MatchRecord m in resultsList.Where(x => x.Rule?.Overrides?.Length > 0))
{
foreach (string idsToOverride in m.Rule?.Overrides ?? Array.Empty<string>())
{
// Find all overriden rules and mark them for removal from issues list
foreach (MatchRecord om in resultsList.FindAll(x => x.Rule?.Id == idsToOverride))
{
// If the overridden match is a subset of the overriding match
if (om.Boundary.Index >= m.Boundary.Index &&
om.Boundary.Index <= m.Boundary.Index + m.Boundary.Length)
{
removes.Add(om);
}
}
}
}
// Remove overriden rules
resultsList.RemoveAll(x => removes.Contains(x));
return resultsList;
}
/// <summary>
/// Analyzes a file and returns a list of <see cref="MatchRecord"/>
/// </summary>
/// <param name="contents">A string containing the text to analyze</param>
/// <param name="fileEntry">FileEntry which has the name of the file being analyzed</param>
/// <param name="languageInfo">The LanguageInfo for the file</param>
/// <param name="tagsToIgnore">Ignore rules that match tags that are only in the tags to ignore list</param>
/// <param name="numLinesContext">Number of lines of text to extract for the sample. Set to 0 to disable context gathering. Set to -1 to also disable sampling the match.</param>
/// <returns>A List of the matches against the Rules the processor is configured with.</returns>
public List<MatchRecord> AnalyzeFile(string contents, FileEntry fileEntry, LanguageInfo languageInfo, IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
TextContainer textContainer = new(contents, languageInfo.Name, _languages, _opts.LoggerFactory ?? NullLoggerFactory.Instance);
return AnalyzeFile(textContainer, fileEntry, languageInfo, tagsToIgnore, numLinesContext);
}
/// <summary>
/// Get the Rules which apply to the FileName of the FileEntry provided.
/// </summary>
/// <param name="languageInfo"></param>
/// <param name="fileEntry"></param>
/// <param name="tagsToIgnore"></param>
/// <returns></returns>
public IEnumerable<ConvertedOatRule> GetRulesForFile(LanguageInfo languageInfo, FileEntry fileEntry, IEnumerable<string>? tagsToIgnore)
{
return GetRulesByLanguage(languageInfo.Name)
.Union(GetRulesByFileName(fileEntry.FullPath))
.Union(GetUniversalRules().Where(x => !x.AppInspectorRule.DoesNotApplyTo?.Contains(languageInfo.Name) ?? true))
.Where(x => !x.AppInspectorRule.Tags?.Any(y => tagsToIgnore?.Contains(y) ?? false) ?? true)
.Where(x => !x.AppInspectorRule.Disabled && SeverityLevel.HasFlag(x.AppInspectorRule.Severity));
}
/// <summary>
/// Analyzes a file and returns a list of <see cref="MatchRecord"/>
/// </summary>
/// <param name="fileEntry">FileEntry which holds the name of the file being analyzed as well as a Stream containing the contents to analyze</param>
/// <param name="languageInfo">The LanguageInfo for the file</param>
/// <param name="tagsToIgnore">Ignore rules that match tags that are only in the tags to ignore list</param>
/// <param name="numLinesContext">Number of lines of text to extract for the sample. Set to 0 to disable context gathering. Set to -1 to also disable sampling the match.</param>
/// <returns>A List of the matches against the Rules the processor is configured with.</returns>
public List<MatchRecord> AnalyzeFile(FileEntry fileEntry, LanguageInfo languageInfo, IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
using var sr = new StreamReader(fileEntry.Content);
var contents = string.Empty;
try
{
contents = sr.ReadToEnd();
}
catch(Exception e)
{
_logger.LogDebug("Failed to analyze file {path}. {type}:{message}. ({stackTrace}), fileRecord.FileName", fileEntry.FullPath, e.GetType(), e.Message, e.StackTrace);
}
return AnalyzeFile(contents, fileEntry, languageInfo, tagsToIgnore, numLinesContext);
}
public async Task<List<MatchRecord>> AnalyzeFileAsync(FileEntry fileEntry, LanguageInfo languageInfo, CancellationToken? cancellationToken = null, IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
var rules = GetRulesForFile(languageInfo, fileEntry, tagsToIgnore);
List<MatchRecord> resultsList = new();
using var sr = new StreamReader(fileEntry.Content);
TextContainer textContainer = new(await sr.ReadToEndAsync().ConfigureAwait(false), languageInfo.Name, _languages, _opts.LoggerFactory ?? NullLoggerFactory.Instance);
foreach (var ruleCapture in _analyzer.GetCaptures(rules, textContainer))
// If a WithinClause capture is present, use only within captures, otherwise just flattens the list of results from the non-within clause.
List<(int, Boundary)> FilterCaptures(List<ClauseCapture> captures)
{
// If we had a WithinClause we only want the captures that passed the within filter.
var filteredCaptures = ruleCapture.Captures.Any(x => x.Clause is WithinClause)
? ruleCapture.Captures.Where(x => x.Clause is WithinClause) : ruleCapture.Captures;
if (cancellationToken?.IsCancellationRequested is true)
if (captures.Any(x => x.Clause is WithinClause))
{
return resultsList;
}
foreach (var cap in filteredCaptures)
{
resultsList.AddRange(ProcessBoundary(cap));
var onlyWithinCaptures = captures.Where(x => x.Clause is WithinClause)
.Cast<TypedClauseCapture<List<(int, Boundary)>>>().ToList();
var allCaptured = onlyWithinCaptures.SelectMany(x => x.Result);
ConcurrentDictionary<(int, Boundary), int> numberOfInstances = new();
// If there are multiple within clauses ensure that we only return matches which passed all clauses
// WithinClauses are always ANDed, but each contains all the captures that passed *that* clause.
// We need the captures that passed every clause.
foreach (var aCapture in allCaptured)
numberOfInstances.AddOrUpdate(aCapture, 1, (tuple, i) => i + 1);
return numberOfInstances.Where(x => x.Value == onlyWithinCaptures.Count).Select(x => x.Key)
.ToList();
}
List<MatchRecord> ProcessBoundary(ClauseCapture cap)
{
List<MatchRecord> newMatches = new();//matches for this rule clause only
var outList = new List<(int, Boundary)>();
foreach (var cap in captures)
if (cap is TypedClauseCapture<List<(int, Boundary)>> tcc)
{
if (ruleCapture.Rule is ConvertedOatRule oatRule)
{
if (tcc.Result is { } captureResults)
outList.AddRange(tcc.Result);
return outList;
}
}
List<MatchRecord> removes = new();
foreach (var m in resultsList.Where(x => x.Rule?.Overrides?.Length > 0))
foreach (var idsToOverride in m.Rule?.Overrides ?? Array.Empty<string>())
// Find all overriden rules and mark them for removal from issues list
foreach (var om in resultsList.FindAll(x => x.Rule?.Id == idsToOverride))
// If the overridden match is a subset of the overriding match
if (om.Boundary.Index >= m.Boundary.Index &&
om.Boundary.Index <= m.Boundary.Index + m.Boundary.Length)
removes.Add(om);
// Remove overriden rules
resultsList.RemoveAll(x => removes.Contains(x));
return resultsList;
}
/// <summary>
/// Analyzes a file and returns a list of <see cref="MatchRecord" />
/// </summary>
/// <param name="contents">A string containing the text to analyze</param>
/// <param name="fileEntry">FileEntry which has the name of the file being analyzed</param>
/// <param name="languageInfo">The LanguageInfo for the file</param>
/// <param name="tagsToIgnore">Ignore rules that match tags that are only in the tags to ignore list</param>
/// <param name="numLinesContext">
/// Number of lines of text to extract for the sample. Set to 0 to disable context gathering.
/// Set to -1 to also disable sampling the match.
/// </param>
/// <returns>A List of the matches against the Rules the processor is configured with.</returns>
public List<MatchRecord> AnalyzeFile(string contents, FileEntry fileEntry, LanguageInfo languageInfo,
IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
TextContainer textContainer = new(contents, languageInfo.Name, _languages,
_opts.LoggerFactory ?? NullLoggerFactory.Instance);
return AnalyzeFile(textContainer, fileEntry, languageInfo, tagsToIgnore, numLinesContext);
}
/// <summary>
/// Get the Rules which apply to the FileName of the FileEntry provided.
/// </summary>
/// <param name="languageInfo"></param>
/// <param name="fileEntry"></param>
/// <param name="tagsToIgnore"></param>
/// <returns></returns>
public IEnumerable<ConvertedOatRule> GetRulesForFile(LanguageInfo languageInfo, FileEntry fileEntry,
IEnumerable<string>? tagsToIgnore)
{
return GetRulesByLanguage(languageInfo.Name)
.Union(GetRulesByFileName(fileEntry.FullPath))
.Union(GetUniversalRules()
.Where(x => !x.AppInspectorRule.DoesNotApplyTo?.Contains(languageInfo.Name) ?? true))
.Where(x => !x.AppInspectorRule.Tags?.Any(y => tagsToIgnore?.Contains(y) ?? false) ?? true)
.Where(x => !x.AppInspectorRule.Disabled && SeverityLevel.HasFlag(x.AppInspectorRule.Severity));
}
/// <summary>
/// Analyzes a file and returns a list of <see cref="MatchRecord" />
/// </summary>
/// <param name="fileEntry">
/// FileEntry which holds the name of the file being analyzed as well as a Stream containing the
/// contents to analyze
/// </param>
/// <param name="languageInfo">The LanguageInfo for the file</param>
/// <param name="tagsToIgnore">Ignore rules that match tags that are only in the tags to ignore list</param>
/// <param name="numLinesContext">
/// Number of lines of text to extract for the sample. Set to 0 to disable context gathering.
/// Set to -1 to also disable sampling the match.
/// </param>
/// <returns>A List of the matches against the Rules the processor is configured with.</returns>
public List<MatchRecord> AnalyzeFile(FileEntry fileEntry, LanguageInfo languageInfo,
IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
using var sr = new StreamReader(fileEntry.Content);
var contents = string.Empty;
try
{
contents = sr.ReadToEnd();
}
catch (Exception e)
{
_logger.LogDebug("Failed to analyze file {path}. {type}:{message}. ({stackTrace}), fileRecord.FileName",
fileEntry.FullPath, e.GetType(), e.Message, e.StackTrace);
}
return AnalyzeFile(contents, fileEntry, languageInfo, tagsToIgnore, numLinesContext);
}
public async Task<List<MatchRecord>> AnalyzeFileAsync(FileEntry fileEntry, LanguageInfo languageInfo,
CancellationToken? cancellationToken = null, IEnumerable<string>? tagsToIgnore = null, int numLinesContext = 3)
{
var rules = GetRulesForFile(languageInfo, fileEntry, tagsToIgnore);
List<MatchRecord> resultsList = new();
using var sr = new StreamReader(fileEntry.Content);
TextContainer textContainer = new(await sr.ReadToEndAsync().ConfigureAwait(false), languageInfo.Name,
_languages, _opts.LoggerFactory ?? NullLoggerFactory.Instance);
foreach (var ruleCapture in _analyzer.GetCaptures(rules, textContainer))
{
// If we had a WithinClause we only want the captures that passed the within filter.
var filteredCaptures = ruleCapture.Captures.Any(x => x.Clause is WithinClause)
? ruleCapture.Captures.Where(x => x.Clause is WithinClause)
: ruleCapture.Captures;
if (cancellationToken?.IsCancellationRequested is true) return resultsList;
foreach (var cap in filteredCaptures) resultsList.AddRange(ProcessBoundary(cap));
List<MatchRecord> ProcessBoundary(ClauseCapture cap)
{
List<MatchRecord> newMatches = new(); //matches for this rule clause only
if (cap is TypedClauseCapture<List<(int, Boundary)>> tcc)
if (ruleCapture.Rule is ConvertedOatRule oatRule)
if (tcc.Result is { } captureResults)
foreach (var match in captureResults)
{
foreach (var match in captureResults)
var patternIndex = match.Item1;
var boundary = match.Item2;
//restrict adds from build files to tags with "metadata" only to avoid false feature positives that are not part of executable code
if (!_opts.AllowAllTagsInBuildFiles &&
languageInfo.Type == LanguageInfo.LangFileType.Build &&
(oatRule.AppInspectorRule.Tags?.Any(v => !v.Contains("Metadata")) ?? false))
continue;
if (patternIndex < 0 || patternIndex > oatRule.AppInspectorRule.Patterns.Length)
{
var patternIndex = match.Item1;
var boundary = match.Item2;
//restrict adds from build files to tags with "metadata" only to avoid false feature positives that are not part of executable code
if (!_opts.AllowAllTagsInBuildFiles && languageInfo.Type == LanguageInfo.LangFileType.Build && (oatRule.AppInspectorRule.Tags?.Any(v => !v.Contains("Metadata")) ?? false))
{
continue;
}
if (patternIndex < 0 || patternIndex > oatRule.AppInspectorRule.Patterns.Length)
{
_logger.LogError("Index out of range for patterns for rule: {ruleName}", oatRule.AppInspectorRule.Name);
continue;
}
if (!_opts.ConfidenceFilter.HasFlag(oatRule.AppInspectorRule.Patterns[patternIndex].Confidence))
{
continue;
}
Location startLocation = textContainer.GetLocation(boundary.Index);
Location endLocation = textContainer.GetLocation(boundary.Index + boundary.Length);
MatchRecord newMatch = new(oatRule.AppInspectorRule)
{
FileName = fileEntry.FullPath,
FullTextContainer = textContainer,
LanguageInfo = languageInfo,
Boundary = boundary,
StartLocationLine = startLocation.Line,
EndLocationLine = endLocation.Line != 0 ? endLocation.Line : startLocation.Line + 1, //match is on last line
MatchingPattern = oatRule.AppInspectorRule.Patterns[patternIndex],
Excerpt = numLinesContext > 0 ? ExtractExcerpt(textContainer, startLocation.Line, numLinesContext) : string.Empty,
Sample = numLinesContext > -1 ? ExtractTextSample(textContainer.FullContent, boundary.Index, boundary.Length) : string.Empty
};
if (oatRule.AppInspectorRule.Tags?.Contains("Dependency.SourceInclude") ?? false)
{
newMatch.Sample = ExtractDependency(newMatch.FullTextContainer, newMatch.Boundary.Index, newMatch.Pattern, newMatch.LanguageInfo.Name);
}
newMatches.Add(newMatch);
_logger.LogError("Index out of range for patterns for rule: {ruleName}",
oatRule.AppInspectorRule.Name);
continue;
}
if (!_opts.ConfidenceFilter.HasFlag(oatRule.AppInspectorRule.Patterns[patternIndex]
.Confidence)) continue;
var startLocation = textContainer.GetLocation(boundary.Index);
var endLocation = textContainer.GetLocation(boundary.Index + boundary.Length);
MatchRecord newMatch = new(oatRule.AppInspectorRule)
{
FileName = fileEntry.FullPath,
FullTextContainer = textContainer,
LanguageInfo = languageInfo,
Boundary = boundary,
StartLocationLine = startLocation.Line,
EndLocationLine =
endLocation.Line != 0
? endLocation.Line
: startLocation.Line + 1, //match is on last line
MatchingPattern = oatRule.AppInspectorRule.Patterns[patternIndex],
Excerpt = numLinesContext > 0
? ExtractExcerpt(textContainer, startLocation.Line, numLinesContext)
: string.Empty,
Sample = numLinesContext > -1
? ExtractTextSample(textContainer.FullContent, boundary.Index, boundary.Length)
: string.Empty
};
if (oatRule.AppInspectorRule.Tags?.Contains("Dependency.SourceInclude") ?? false)
newMatch.Sample = ExtractDependency(newMatch.FullTextContainer,
newMatch.Boundary.Index, newMatch.Pattern, newMatch.LanguageInfo.Name);
newMatches.Add(newMatch);
}
}
}
return newMatches;
}
return newMatches;
}
List<MatchRecord> removes = new();
foreach (MatchRecord m in resultsList.Where(x => x.Rule?.Overrides?.Length > 0))
{
if (cancellationToken?.IsCancellationRequested is true)
{
return resultsList;
}
foreach (string ovrd in m.Rule?.Overrides ?? Array.Empty<string>())
{
// Find all overriden rules and mark them for removal from issues list
foreach (MatchRecord om in resultsList.FindAll(x => x.Rule?.Id == ovrd))
{
if (om.Boundary.Index >= m.Boundary.Index &&
om.Boundary.Index <= m.Boundary.Index + m.Boundary.Length)
{
removes.Add(om);
}
}
}
}
// Remove overriden rules
resultsList.RemoveAll(x => removes.Contains(x));
return resultsList;
}
/// <summary>
/// Filters the rules for those matching the specified language.
/// </summary>
/// <param name="language"> Language to filter rules for </param>
/// <returns> List of rules </returns>
private IEnumerable<ConvertedOatRule> GetRulesByLanguage(string language)
List<MatchRecord> removes = new();
foreach (var m in resultsList.Where(x => x.Rule?.Overrides?.Length > 0))
{
if (cancellationToken?.IsCancellationRequested is true) return resultsList;
foreach (var ovrd in m.Rule?.Overrides ?? Array.Empty<string>())
// Find all overriden rules and mark them for removal from issues list
foreach (var om in resultsList.FindAll(x => x.Rule?.Id == ovrd))
if (om.Boundary.Index >= m.Boundary.Index &&
om.Boundary.Index <= m.Boundary.Index + m.Boundary.Length)
removes.Add(om);
}
// Remove overriden rules
resultsList.RemoveAll(x => removes.Contains(x));
return resultsList;
}
/// <summary>
/// Filters the rules for those matching the specified language.
/// </summary>
/// <param name="language"> Language to filter rules for </param>
/// <returns> List of rules </returns>
private IEnumerable<ConvertedOatRule> GetRulesByLanguage(string language)
{
if (EnableCache)
if (_languageRulesCache.ContainsKey(language))
return _languageRulesCache[language];
IEnumerable<ConvertedOatRule> filteredRules = _ruleset.ByLanguage(language).ToArray();
if (EnableCache) _languageRulesCache.TryAdd(language, filteredRules);
return filteredRules;
}
/// <summary>
/// Get all rules that apply to all files.
/// </summary>
/// <returns> List of rules </returns>
private IEnumerable<ConvertedOatRule> GetUniversalRules()
{
if (_universalRulesCache is null)
{
if (EnableCache)
{
if (_languageRulesCache.ContainsKey(language))
return _languageRulesCache[language];
}
IEnumerable<ConvertedOatRule> filteredRules = _ruleset.ByLanguage(language).ToArray();
if (EnableCache)
{
_languageRulesCache.TryAdd(language, filteredRules);
}
return filteredRules;
_universalRulesCache = _ruleset.GetUniversalRules();
else
return _ruleset.GetUniversalRules();
}
/// <summary>
/// Get all rules that apply to all files.
/// </summary>
/// <returns> List of rules </returns>
private IEnumerable<ConvertedOatRule> GetUniversalRules()
{
if (_universalRulesCache is null)
{
if (EnableCache)
{
_universalRulesCache = _ruleset.GetUniversalRules();
}
else
{
return _ruleset.GetUniversalRules();
}
}
return _universalRulesCache;
}
return _universalRulesCache;
}
/// <summary>
/// Filters the rules for those matching the filename.
/// </summary>
/// <param name="fileName"> Filename to filter for</param>
/// <returns> List of rules </returns>
private IEnumerable<ConvertedOatRule> GetRulesByFileName(string fileName)
{
if (EnableCache)
if (_fileRulesCache.ContainsKey(fileName))
return _fileRulesCache[fileName];
/// <summary>
/// Filters the rules for those matching the filename.
/// </summary>
/// <param name="fileName"> Filename to filter for</param>
/// <returns> List of rules </returns>
private IEnumerable<ConvertedOatRule> GetRulesByFileName(string fileName)
{
if (EnableCache)
{
if (_fileRulesCache.ContainsKey(fileName))
return _fileRulesCache[fileName];
}
IEnumerable<ConvertedOatRule> filteredRules = _ruleset.ByFilename(fileName).ToArray();
IEnumerable<ConvertedOatRule> filteredRules = _ruleset.ByFilename(fileName).ToArray();
if (EnableCache) _fileRulesCache.TryAdd(fileName, filteredRules);
if (EnableCache)
{
_fileRulesCache.TryAdd(fileName, filteredRules);
}
return filteredRules;
}
return filteredRules;
}
/// <summary>
/// Simple wrapper but keeps calling code consistent
/// Do not html code result which is accomplished later before out put to report
/// </summary>
private string ExtractTextSample(string fileText, int index, int length)
{
if (index < 0 || length < 0) return fileText;
/// <summary>
/// Simple wrapper but keeps calling code consistent
/// Do not html code result which is accomplished later before out put to report
/// </summary>
private string ExtractTextSample(string fileText, int index, int length)
{
if (index < 0 || length < 0) { return fileText; }
length = Math.Min(Math.Min(length, MAX_TEXT_SAMPLE_LENGTH), fileText.Length - index);
length = Math.Min(Math.Min(length, MAX_TEXT_SAMPLE_LENGTH), fileText.Length - index);
if (length == 0) return string.Empty;
if (length == 0) { return string.Empty; }
return fileText[index..(index + length)].Trim();
}
return fileText[index..(index + length)].Trim();
}
/// <summary>
/// Located here to include during Match creation to avoid a call later or putting in constructor
/// Needed in match ensuring value exists at time of report writing rather than expecting a callback
/// from the template
/// </summary>
/// <returns></returns>
private static string ExtractExcerpt(TextContainer text, int startLineNumber, int context = 3)
{
if (context == 0) return string.Empty;
if (startLineNumber < 0) startLineNumber = 0;
/// <summary>
/// Located here to include during Match creation to avoid a call later or putting in constructor
/// Needed in match ensuring value exists at time of report writing rather than expecting a callback
/// from the template
/// </summary>
/// <returns></returns>
private static string ExtractExcerpt(TextContainer text, int startLineNumber, int context = 3)
{
if (context == 0)
{
return string.Empty;
}
if (startLineNumber < 0)
{
startLineNumber = 0;
}
if (startLineNumber >= text.LineEnds.Count) startLineNumber = text.LineEnds.Count - 1;
if (startLineNumber >= text.LineEnds.Count)
{
startLineNumber = text.LineEnds.Count - 1;
}
var excerptStartLine = Math.Max(0, startLineNumber - context);
var excerptEndLine = Math.Min(text.LineEnds.Count - 1, startLineNumber + context);
var startIndex = text.LineStarts[excerptStartLine];
var endIndex = text.LineEnds[excerptEndLine] + 1;
var maxCharacterContext = context * 100;
// Only gather 100*lines context characters to avoid gathering super long lines
if (text.LineStarts[startLineNumber] - startIndex > maxCharacterContext)
{
startIndex = Math.Max(0, startIndex - maxCharacterContext);
}
if (endIndex - text.LineEnds[startLineNumber] > maxCharacterContext)
{
endIndex = Math.Min(text.FullContent.Length - 1, endIndex + maxCharacterContext);
}
return text.FullContent[startIndex..endIndex];
}
}
}
var excerptStartLine = Math.Max(0, startLineNumber - context);
var excerptEndLine = Math.Min(text.LineEnds.Count - 1, startLineNumber + context);
var startIndex = text.LineStarts[excerptStartLine];
var endIndex = text.LineEnds[excerptEndLine] + 1;
var maxCharacterContext = context * 100;
// Only gather 100*lines context characters to avoid gathering super long lines
if (text.LineStarts[startLineNumber] - startIndex > maxCharacterContext)
startIndex = Math.Max(0, startIndex - maxCharacterContext);
if (endIndex - text.LineEnds[startLineNumber] > maxCharacterContext)
endIndex = Math.Min(text.FullContent.Length - 1, endIndex + maxCharacterContext);
return text.FullContent[startIndex..endIndex];
}
}

Просмотреть файл

@ -14,211 +14,191 @@ using Microsoft.CST.OAT;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Common helper used by VerifyRulesCommand and PackRulesCommand classes to reduce duplication
/// </summary>
public class RulesVerifier
{
/// <summary>
/// Common helper used by VerifyRulesCommand and PackRulesCommand classes to reduce duplication
/// </summary>
public class RulesVerifier
private readonly Analyzer _analyzer;
private readonly ILogger _logger;
private readonly RulesVerifierOptions _options;
public RulesVerifier(RulesVerifierOptions options)
{
private readonly ILogger _logger;
private readonly RulesVerifierOptions _options;
private ILoggerFactory? _loggerFactory => _options.LoggerFactory;
private readonly Analyzer _analyzer;
public RulesVerifier(RulesVerifierOptions options)
_options = options;
_logger = _options.LoggerFactory?.CreateLogger<RulesVerifier>() ?? NullLogger<RulesVerifier>.Instance;
_analyzer = _options.Analyzer ?? new ApplicationInspectorAnalyzer(_options.LoggerFactory);
}
private ILoggerFactory? _loggerFactory => _options.LoggerFactory;
/// <summary>
/// Compile ruleset from a path to a directory or file containing a rule.json file and verify the status of the rules.
/// </summary>
/// <param name="fileName">Path to rules.</param>
/// <returns></returns>
/// <exception cref="OpException"></exception>
public RulesVerifierResult Verify(string rulesPath)
{
RuleSet CompiledRuleset = new(_loggerFactory);
if (!string.IsNullOrEmpty(rulesPath))
{
_options = options;
_logger = _options.LoggerFactory?.CreateLogger<RulesVerifier>() ?? NullLogger<RulesVerifier>.Instance;
_analyzer = _options.Analyzer ?? new ApplicationInspectorAnalyzer(_options.LoggerFactory);
if (Directory.Exists(rulesPath))
CompiledRuleset.AddDirectory(rulesPath);
else if (File.Exists(rulesPath))
CompiledRuleset.AddFile(rulesPath);
else
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_RULE_PATH, rulesPath));
}
/// <summary>
/// Compile ruleset from a path to a directory or file containing a rule.json file and verify the status of the rules.
/// </summary>
/// <param name="fileName">Path to rules.</param>
/// <returns></returns>
/// <exception cref="OpException"></exception>
public RulesVerifierResult Verify(string rulesPath)
{
RuleSet CompiledRuleset = new(_loggerFactory);
return Verify(CompiledRuleset);
}
if (!string.IsNullOrEmpty(rulesPath))
public RulesVerifierResult Verify(AbstractRuleSet ruleset)
{
return new RulesVerifierResult(CheckIntegrity(ruleset), ruleset);
}
public List<RuleStatus> CheckIntegrity(AbstractRuleSet ruleSet)
{
List<RuleStatus> ruleStatuses = new();
foreach (var rule in ruleSet.GetOatRules())
{
var ruleVerified = CheckIntegrity(rule);
ruleStatuses.Add(ruleVerified);
}
if (!_options.DisableRequireUniqueIds)
{
var duplicatedRules = ruleSet.GetAppInspectorRules().GroupBy(x => x.Id).Where(y => y.Count() > 1);
foreach (var rule in duplicatedRules)
{
if (Directory.Exists(rulesPath))
{
CompiledRuleset.AddDirectory(rulesPath);
}
else if (File.Exists(rulesPath))
{
CompiledRuleset.AddFile(rulesPath);
}
else
{
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_RULE_PATH, rulesPath));
}
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_DUPLICATEID_FAIL), rule.Key);
var relevantStati = ruleStatuses.Where(x => x.RulesId == rule.Key);
foreach (var status in relevantStati)
status.Errors =
status.Errors.Append(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_DUPLICATEID_FAIL, rule.Key));
}
return Verify(CompiledRuleset);
}
public RulesVerifierResult Verify(AbstractRuleSet ruleset)
return ruleStatuses;
}
public RuleStatus CheckIntegrity(ConvertedOatRule convertedOatRule)
{
List<string> errors = new();
// App Inspector checks
var rule = convertedOatRule.AppInspectorRule;
// Check for null Id
if (string.IsNullOrEmpty(rule.Id))
{
return new RulesVerifierResult(CheckIntegrity(ruleset), ruleset);
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_NULLID_FAIL), rule.Name);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_NULLID_FAIL, rule.Name));
}
public List<RuleStatus> CheckIntegrity(AbstractRuleSet ruleSet)
//applicability
if (rule.AppliesTo != null)
{
List<RuleStatus> ruleStatuses = new();
foreach (ConvertedOatRule rule in ruleSet.GetOatRules())
{
RuleStatus ruleVerified = CheckIntegrity(rule);
ruleStatuses.Add(ruleVerified);
}
if (!_options.DisableRequireUniqueIds)
{
var duplicatedRules = ruleSet.GetAppInspectorRules().GroupBy(x => x.Id).Where(y => y.Count() > 1);
foreach (var rule in duplicatedRules)
{
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_DUPLICATEID_FAIL), rule.Key);
var relevantStati = ruleStatuses.Where(x => x.RulesId == rule.Key);
foreach(var status in relevantStati)
var languages = _options.LanguageSpecs.GetNames();
// Check for unknown language
foreach (var lang in rule.AppliesTo)
if (!string.IsNullOrEmpty(lang))
if (!languages.Any(x => x.Equals(lang, StringComparison.CurrentCultureIgnoreCase)))
{
status.Errors = status.Errors.Append(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_DUPLICATEID_FAIL, rule.Key));
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_LANGUAGE_FAIL), rule.Id ?? "", lang);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_LANGUAGE_FAIL, rule.Id ?? "", lang));
}
}
}
return ruleStatuses;
}
public RuleStatus CheckIntegrity(ConvertedOatRule convertedOatRule)
foreach (var pattern in rule.FileRegexes ?? Array.Empty<string>())
try
{
_ = new Regex(pattern, RegexOptions.Compiled);
}
catch (Exception e)
{
_logger?.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL), rule.Id ?? "", pattern ?? "",
e.Message);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL, rule.Id ?? "", pattern ?? "",
e.Message));
}
//valid search pattern
foreach (var searchPattern in rule.Patterns ?? Array.Empty<SearchPattern>())
{
List<string> errors = new();
// App Inspector checks
var rule = convertedOatRule.AppInspectorRule;
// Check for null Id
if (string.IsNullOrEmpty(rule.Id))
{
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_NULLID_FAIL), rule.Name);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_NULLID_FAIL, rule.Name));
}
//applicability
if (rule.AppliesTo != null)
{
string[] languages = _options.LanguageSpecs.GetNames();
// Check for unknown language
foreach (string lang in rule.AppliesTo)
{
if (!string.IsNullOrEmpty(lang))
{
if (!languages.Any(x => x.Equals(lang, StringComparison.CurrentCultureIgnoreCase)))
{
_logger.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_LANGUAGE_FAIL), rule.Id ?? "", lang);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_LANGUAGE_FAIL, rule.Id ?? "", lang));
}
}
}
}
foreach (var pattern in rule.FileRegexes ?? Array.Empty<string>())
{
if (searchPattern.PatternType == PatternType.RegexWord || searchPattern.PatternType == PatternType.Regex)
try
{
_ = new Regex(pattern, RegexOptions.Compiled);
if (string.IsNullOrEmpty(searchPattern.Pattern)) throw new ArgumentException();
_ = new Regex(searchPattern.Pattern);
}
catch (Exception e)
{
_logger?.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL), rule.Id ?? "", pattern ?? "", e.Message);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL, rule.Id ?? "", pattern ?? "", e.Message));
_logger?.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL), rule.Id ?? "",
searchPattern.Pattern ?? "", e.Message);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL, rule.Id ?? "",
searchPattern.Pattern ?? "", e.Message));
}
}
//valid search pattern
foreach (SearchPattern searchPattern in rule.Patterns ?? Array.Empty<SearchPattern>())
{
if (searchPattern.PatternType == PatternType.RegexWord || searchPattern.PatternType == PatternType.Regex)
{
if (searchPattern.JsonPaths is not null)
foreach (var jsonPath in searchPattern.JsonPaths)
try
{
if (string.IsNullOrEmpty(searchPattern.Pattern))
{
throw new ArgumentException();
}
_ = new Regex(searchPattern.Pattern);
_ = JsonSelector.Parse(jsonPath);
}
catch (Exception e)
{
_logger?.LogError(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL), rule.Id ?? "", searchPattern.Pattern ?? "", e.Message);
errors.Add(MsgHelp.FormatString(MsgHelp.ID.VERIFY_RULES_REGEX_FAIL, rule.Id ?? "", searchPattern.Pattern ?? "", e.Message));
_logger?.LogError(
"The provided JsonPath '{JsonPath}' value was not valid in Rule {Id} : {message}",
searchPattern.JsonPaths, rule.Id, e.Message);
errors.Add(string.Format("The provided JsonPath '{0}' value was not valid in Rule {1} : {2}",
searchPattern.JsonPaths, rule.Id, e.Message));
}
}
if (searchPattern.JsonPaths is not null)
{
foreach (var jsonPath in searchPattern.JsonPaths)
if (searchPattern.XPaths is not null)
foreach (var xpath in searchPattern.XPaths)
try
{
try
{
_ = JsonSelector.Parse(jsonPath);
}
catch (Exception e)
{
_logger?.LogError("The provided JsonPath '{JsonPath}' value was not valid in Rule {Id} : {message}", searchPattern.JsonPaths, rule.Id, e.Message);
errors.Add(string.Format("The provided JsonPath '{0}' value was not valid in Rule {1} : {2}", searchPattern.JsonPaths, rule.Id, e.Message));
}
XPathExpression.Compile(xpath);
}
}
if (searchPattern.XPaths is not null)
{
foreach (var xpath in searchPattern.XPaths)
catch (Exception e)
{
try
{
XPathExpression.Compile(xpath);
}
catch (Exception e)
{
_logger?.LogError("The provided XPath '{XPath}' value was not valid in Rule {Id} : {message}", searchPattern.XPaths, rule.Id, e.Message);
errors.Add(string.Format("The provided XPath '{0}' value was not valid in Rule {1} : {2}", searchPattern.JsonPaths, rule.Id, e.Message));
}
_logger?.LogError("The provided XPath '{XPath}' value was not valid in Rule {Id} : {message}",
searchPattern.XPaths, rule.Id, e.Message);
errors.Add(string.Format("The provided XPath '{0}' value was not valid in Rule {1} : {2}",
searchPattern.JsonPaths, rule.Id, e.Message));
}
}
}
}
// validate conditions
foreach(var condition in rule.Conditions ?? Array.Empty<SearchCondition>())
// validate conditions
foreach (var condition in rule.Conditions ?? Array.Empty<SearchCondition>())
if (condition.SearchIn is null)
{
if (condition.SearchIn is null)
_logger?.LogError("SearchIn is null in {ruleId}", rule.Id);
errors.Add($"SearchIn is null in {rule.Id}");
}
else if (condition.SearchIn.StartsWith("finding-region"))
{
var parSplits = condition.SearchIn.Split(')', '(');
if (parSplits.Length == 3)
{
_logger?.LogError("SearchIn is null in {ruleId}",rule.Id);
errors.Add($"SearchIn is null in {rule.Id}");
}
else if (condition.SearchIn.StartsWith("finding-region"))
{
var parSplits = condition.SearchIn.Split(new char[] { ')', '(' });
if (parSplits.Length == 3)
var splits = parSplits[1].Split(',');
if (splits.Length == 2)
{
var splits = parSplits[1].Split(',');
if (splits.Length == 2)
{
if (int.TryParse(splits[0], out int int1) && int.TryParse(splits[1], out int int2))
if (int.TryParse(splits[0], out var int1) && int.TryParse(splits[1], out var int2))
if (int1 > 0 && int2 < 0)
{
if (int1 > 0 && int2 < 0)
{
_logger?.LogError("The finding region must have a negative number or 0 for the lines before and a positive number or 0 for lines after. {0}", rule.Id);
errors.Add(
$"The finding region must have a negative number or 0 for the lines before and a positive number or 0 for lines after. {rule.Id}");
}
_logger?.LogError(
"The finding region must have a negative number or 0 for the lines before and a positive number or 0 for lines after. {0}",
rule.Id);
errors.Add(
$"The finding region must have a negative number or 0 for the lines before and a positive number or 0 for lines after. {rule.Id}");
}
}
else
{
_logger?.LogError("Improperly specified finding region. {id}", rule.Id);
errors.Add($"Improperly specified finding region. {rule.Id}");
}
}
else
{
@ -226,70 +206,75 @@ namespace Microsoft.ApplicationInspector.RulesEngine
errors.Add($"Improperly specified finding region. {rule.Id}");
}
}
}
var singleList = new [] {convertedOatRule};
// We need to provide a language for the TextContainer, which will later be referenced by the Rule when executed.
// We can grab any Language that the rule applies to, if there are none, it means it applies to all languages, except any in DoesNotApplyTo.
// Then we fall back to grab any language from the languages configuration that isn't DoesNotApplyTo for this rule.
var language = convertedOatRule.AppInspectorRule.AppliesTo?.FirstOrDefault() ??
_options.LanguageSpecs.GetNames().FirstOrDefault(x => !convertedOatRule.AppInspectorRule.DoesNotApplyTo?.Contains(x, StringComparer.InvariantCultureIgnoreCase) ?? true) ?? "csharp";
// validate all must match samples are matched
foreach (var mustMatchElement in rule.MustMatch ?? Array.Empty<string>())
{
var tc = new TextContainer(mustMatchElement, language, _options.LanguageSpecs);
if (!_analyzer.Analyze(singleList, tc).Any())
else
{
_logger?.LogError("Rule {ID} does not match the 'MustMatch' test {MustMatch}. ", rule.Id, mustMatchElement);
errors.Add($"Rule {rule.Id} does not match the 'MustMatch' test {mustMatchElement}. ");
_logger?.LogError("Improperly specified finding region. {id}", rule.Id);
errors.Add($"Improperly specified finding region. {rule.Id}");
}
}
// validate no must not match conditions are matched
foreach (var mustNotMatchElement in rule.MustNotMatch ?? Array.Empty<string>())
{
var tc = new TextContainer(mustNotMatchElement, language, _options.LanguageSpecs);
if (_analyzer.Analyze(singleList, tc).Any())
{
_logger?.LogError("Rule {ID} matches the 'MustNotMatch' test '{MustNotMatch}'. ", rule.Id, mustNotMatchElement);
errors.Add($"Rule {rule.Id} matches the 'MustNotMatch' test '{mustNotMatchElement}'.");
}
}
var singleList = new[] { convertedOatRule };
if (rule.Tags?.Length == 0)
{
_logger?.LogError("Rule must specify tags. {0}", rule.Id);
errors.Add($"Rule must specify tags. {rule.Id}");
}
// We need to provide a language for the TextContainer, which will later be referenced by the Rule when executed.
// We can grab any Language that the rule applies to, if there are none, it means it applies to all languages, except any in DoesNotApplyTo.
// Then we fall back to grab any language from the languages configuration that isn't DoesNotApplyTo for this rule.
var language = convertedOatRule.AppInspectorRule.AppliesTo?.FirstOrDefault() ??
_options.LanguageSpecs.GetNames().FirstOrDefault(x =>
!convertedOatRule.AppInspectorRule.DoesNotApplyTo?.Contains(x,
StringComparer.InvariantCultureIgnoreCase) ?? true) ?? "csharp";
if (_options.RequireMustMatch)
// validate all must match samples are matched
foreach (var mustMatchElement in rule.MustMatch ?? Array.Empty<string>())
{
var tc = new TextContainer(mustMatchElement, language, _options.LanguageSpecs);
if (!_analyzer.Analyze(singleList, tc).Any())
{
if (rule.MustMatch?.Any() is not true)
{
_logger?.LogError("Rule must specify MustMatch when `RequireMustMatch` is set. {0}", rule.Id);
errors.Add($"Rule must specify MustMatch when `RequireMustMatch` is set. {rule.Id}");
}
_logger?.LogError("Rule {ID} does not match the 'MustMatch' test {MustMatch}. ", rule.Id,
mustMatchElement);
errors.Add($"Rule {rule.Id} does not match the 'MustMatch' test {mustMatchElement}. ");
}
if (_options.RequireMustNotMatch)
{
if (rule.MustNotMatch?.Any() is not true)
{
_logger?.LogError("Rule must specify MustNotMatch when `RequireMustNotMatch` is set. {0}", rule.Id);
errors.Add($"Rule must specify MustNotMatch when `RequireMustNotMatch` is set. {rule.Id}");
}
}
return new RuleStatus()
{
RulesId = rule.Id,
RulesName = rule.Name,
Errors = errors,
OatIssues = _analyzer.EnumerateRuleIssues(convertedOatRule),
HasPositiveSelfTests = rule.MustMatch?.Length > 0,
HasNegativeSelfTests = rule.MustNotMatch?.Length > 0
};
}
// validate no must not match conditions are matched
foreach (var mustNotMatchElement in rule.MustNotMatch ?? Array.Empty<string>())
{
var tc = new TextContainer(mustNotMatchElement, language, _options.LanguageSpecs);
if (_analyzer.Analyze(singleList, tc).Any())
{
_logger?.LogError("Rule {ID} matches the 'MustNotMatch' test '{MustNotMatch}'. ", rule.Id,
mustNotMatchElement);
errors.Add($"Rule {rule.Id} matches the 'MustNotMatch' test '{mustNotMatchElement}'.");
}
}
if (rule.Tags?.Length == 0)
{
_logger?.LogError("Rule must specify tags. {0}", rule.Id);
errors.Add($"Rule must specify tags. {rule.Id}");
}
if (_options.RequireMustMatch)
if (rule.MustMatch?.Any() is not true)
{
_logger?.LogError("Rule must specify MustMatch when `RequireMustMatch` is set. {0}", rule.Id);
errors.Add($"Rule must specify MustMatch when `RequireMustMatch` is set. {rule.Id}");
}
if (_options.RequireMustNotMatch)
if (rule.MustNotMatch?.Any() is not true)
{
_logger?.LogError("Rule must specify MustNotMatch when `RequireMustNotMatch` is set. {0}", rule.Id);
errors.Add($"Rule must specify MustNotMatch when `RequireMustNotMatch` is set. {rule.Id}");
}
return new RuleStatus
{
RulesId = rule.Id,
RulesName = rule.Name,
Errors = errors,
OatIssues = _analyzer.EnumerateRuleIssues(convertedOatRule),
HasPositiveSelfTests = rule.MustMatch?.Length > 0,
HasNegativeSelfTests = rule.MustNotMatch?.Length > 0
};
}
}

Просмотреть файл

@ -6,30 +6,34 @@ namespace Microsoft.ApplicationInspector.RulesEngine;
public class RulesVerifierOptions
{
/// <summary>
/// If desired you may provide the analyzer to use. An analyzer with AI defaults will be created to use for validation.
/// If desired you may provide the analyzer to use. An analyzer with AI defaults will be created to use for validation.
/// </summary>
public Analyzer? Analyzer { get; set; }
/// <summary>
/// To receive log messages, provide a LoggerFactory with your preferred configuration.
/// </summary>
public ILoggerFactory? LoggerFactory { get; set; }
/// <summary>
/// The language specifications to use
/// </summary>
public Languages LanguageSpecs { get; set; } = new Languages();
/// <summary>
/// By default rules must have unique IDs, this disables that validation check
/// To receive log messages, provide a LoggerFactory with your preferred configuration.
/// </summary>
public ILoggerFactory? LoggerFactory { get; set; }
/// <summary>
/// The language specifications to use
/// </summary>
public Languages LanguageSpecs { get; set; } = new();
/// <summary>
/// By default rules must have unique IDs, this disables that validation check
/// </summary>
public bool DisableRequireUniqueIds { get; set; }
/// <summary>
/// By default, the <see cref="RuleStatus.HasPositiveSelfTests"/> property informs if <see cref="Rule.MustMatch"/> is populated. Enabling this will cause an error to be raised during rule validation if it is not populated.
/// By default, the <see cref="RuleStatus.HasPositiveSelfTests" /> property informs if <see cref="Rule.MustMatch" /> is
/// populated. Enabling this will cause an error to be raised during rule validation if it is not populated.
/// </summary>
public bool RequireMustMatch { get; set; }
/// <summary>
/// By default, the <see cref="RuleStatus.HasNegativeSelfTests"/> property informs if <see cref="Rule.MustNotMatch"/> is populated. Enabling this will cause an error to be raised during rule validation if it is not populated.
/// By default, the <see cref="RuleStatus.HasNegativeSelfTests" /> property informs if <see cref="Rule.MustNotMatch" />
/// is populated. Enabling this will cause an error to be raised during rule validation if it is not populated.
/// </summary>
public bool RequireMustNotMatch { get; set; }
}

Просмотреть файл

@ -10,7 +10,8 @@ public class RulesVerifierResult
RuleStatuses = ruleStatuses;
CompiledRuleSet = compiledRuleSets;
}
public List<RuleStatus> RuleStatuses { get; }
public AbstractRuleSet CompiledRuleSet { get; }
public bool Verified => RuleStatuses.All(x => x.Verified);
public bool Verified => RuleStatuses.All(x => x.Verified);
}

Просмотреть файл

@ -1,22 +1,20 @@
// Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License.
namespace Microsoft.ApplicationInspector.RulesEngine
using Microsoft.Extensions.Logging;
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Default class to use to store Application Inspector <see cref="Rule" /> objects.
/// </summary>
public class RuleSet : TypedRuleSet<Rule>
{
using Microsoft.Extensions.Logging;
/// <summary>
/// Default class to use to store Application Inspector <see cref="Rule"/> objects.
/// Create a ruleset using the given (optional) logger.
/// </summary>
public class RuleSet : TypedRuleSet<Rule>
/// <param name="loggerFactory"></param>
public RuleSet(ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
/// <summary>
/// Create a ruleset using the given (optional) logger.
/// </summary>
/// <param name="loggerFactory"></param>
public RuleSet(ILoggerFactory? loggerFactory = null) : base(loggerFactory)
{
}
}
}
}

Просмотреть файл

@ -2,18 +2,16 @@
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
public class SearchCondition
{
[JsonProperty(PropertyName = "negate_finding")]
public bool NegateFinding { get; set; }
public class SearchCondition
{
[JsonProperty(PropertyName ="negate_finding")]
public bool NegateFinding { get; set; }
[JsonProperty(PropertyName = "pattern")]
public SearchPattern? Pattern { get; set; }
[JsonProperty(PropertyName ="pattern")]
public SearchPattern? Pattern { get; set; }
[JsonProperty(PropertyName ="search_in")]
public string? SearchIn { get; set; }
}
[JsonProperty(PropertyName = "search_in")]
public string? SearchIn { get; set; }
}

Просмотреть файл

@ -1,61 +1,56 @@
// Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License.
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Class to hold search pattern
/// </summary>
public class SearchPattern
{
using System.Collections.Generic;
using System.Text.RegularExpressions;
private readonly Dictionary<RegexOptions, Regex> _compiled = new();
private string? _pattern;
[JsonProperty(PropertyName = "confidence")]
[JsonConverter(typeof(StringEnumConverter))]
public Confidence Confidence { get; set; }
[JsonProperty(PropertyName = "modifiers")]
public string[]? Modifiers { get; set; }
[JsonProperty(PropertyName = "pattern")]
public string? Pattern
{
get => _pattern;
set
{
_compiled.Clear();
_pattern = value;
}
}
[JsonProperty(PropertyName = "type")]
[JsonConverter(typeof(StringEnumConverter))]
public PatternType? PatternType { get; set; }
[JsonProperty(PropertyName = "scopes")]
public PatternScope[]? Scopes { get; set; }
/// <summary>
/// Class to hold search pattern
/// If set, attempt to parse the file as XML and if that is possible,
/// before running the pattern, select down to the XPath provided
/// </summary>
public class SearchPattern
{
private Dictionary<RegexOptions, Regex> _compiled = new();
private string? _pattern;
[JsonProperty(PropertyName = "xpaths")]
public string[]? XPaths { get; set; }
[JsonProperty(PropertyName ="confidence")]
[JsonConverter(typeof(StringEnumConverter))]
public Confidence Confidence { get; set; }
[JsonProperty(PropertyName ="modifiers")]
public string[]? Modifiers { get; set; }
[JsonProperty(PropertyName ="pattern")]
public string? Pattern
{
get
{
return _pattern;
}
set
{
_compiled.Clear();
_pattern = value;
}
}
[JsonProperty(PropertyName ="type")]
[JsonConverter(typeof(StringEnumConverter))]
public PatternType? PatternType { get; set; }
[JsonProperty(PropertyName ="scopes")]
public PatternScope[]? Scopes { get; set; }
/// <summary>
/// If set, attempt to parse the file as XML and if that is possible,
/// before running the pattern, select down to the XPath provided
/// </summary>
[JsonProperty(PropertyName ="xpaths")]
public string[]? XPaths { get; set; }
/// <summary>
/// If set, attempt to parse the file as JSON and if that is possible,
/// before running the pattern, select down to the JsonPath provided
/// </summary>
[JsonProperty(PropertyName = "jsonpaths")]
public string[]? JsonPaths { get; set; }
}
/// <summary>
/// If set, attempt to parse the file as JSON and if that is possible,
/// before running the pattern, select down to the JsonPath provided
/// </summary>
[JsonProperty(PropertyName = "jsonpaths")]
public string[]? JsonPaths { get; set; }
}

Просмотреть файл

@ -1,46 +1,45 @@
// Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License.
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Issue severity
/// </summary>
[Flags]
[JsonConverter(typeof(StringEnumConverter))]
public enum Severity
{
using System;
/// <summary>
/// Has not been specified
/// </summary>
Unspecified = 0,
/// <summary>
/// Issue severity
/// Critical issues
/// </summary>
[Flags]
[JsonConverter(typeof(StringEnumConverter))]
public enum Severity
{
/// <summary>
/// Has not been specified
/// </summary>
Unspecified = 0,
/// <summary>
/// Critical issues
/// </summary>
Critical = 1,
Critical = 1,
/// <summary>
/// Important issues
/// </summary>
Important = 2,
/// <summary>
/// Important issues
/// </summary>
Important = 2,
/// <summary>
/// Moderate issues
/// </summary>
Moderate = 4,
/// <summary>
/// Moderate issues
/// </summary>
Moderate = 4,
/// <summary>
/// Best Practice
/// </summary>
BestPractice = 8,
/// <summary>
/// Best Practice
/// </summary>
BestPractice = 8,
/// <summary>
/// Issues that require manual review
/// </summary>
ManualReview = 16
}
/// <summary>
/// Issues that require manual review
/// </summary>
ManualReview = 16
}

Просмотреть файл

@ -1,370 +1,319 @@
// Copyright (C) Microsoft. All rights reserved. Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Xml.XPath;
using JsonCons.JsonPath;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.ApplicationInspector.RulesEngine
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// Class to handle text as a searchable container
/// </summary>
public class TextContainer
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Xml.XPath;
private readonly ILogger _logger;
private readonly string inline;
private readonly string prefix;
private readonly string suffix;
private JsonDocument? _jsonDocument;
private bool _triedToConstructJsonDocument;
private bool _triedToConstructXPathDocument;
private XPathDocument? _xmlDoc;
/// <summary>
/// Class to handle text as a searchable container
/// Creates new instance
/// </summary>
public class TextContainer
/// <param name="content"> Text to work with </param>
/// <param name="language"> The language of the test </param>
/// <param name="languages">
/// An instance of the <see cref="Languages" /> class containing the information for language
/// mapping to use.
/// </param>
public TextContainer(string content, string language, Languages languages, ILoggerFactory? loggerFactory = null)
{
/// <summary>
/// Creates new instance
/// </summary>
/// <param name="content"> Text to work with </param>
/// <param name="language"> The language of the test </param>
/// <param name="languages">An instance of the <see cref="Languages"/> class containing the information for language mapping to use.</param>
public TextContainer(string content, string language, Languages languages, ILoggerFactory? loggerFactory = null)
_logger = loggerFactory?.CreateLogger<TextContainer>() ?? NullLogger<TextContainer>.Instance;
Language = language;
FullContent = content;
LineEnds = new List<int> { 0 };
LineStarts = new List<int> { 0, 0 };
// Find line end in the text
var pos = FullContent.IndexOf('\n');
while (pos > -1)
{
_logger = loggerFactory?.CreateLogger<TextContainer>() ?? NullLogger<TextContainer>.Instance;
Language = language;
FullContent = content;
LineEnds = new List<int>() { 0 };
LineStarts = new List<int>() { 0, 0 };
LineEnds.Add(pos);
// Find line end in the text
int pos = FullContent.IndexOf('\n');
while (pos > -1)
{
LineEnds.Add(pos);
if (pos + 1 < FullContent.Length) LineStarts.Add(pos + 1);
if (pos + 1 < FullContent.Length)
{
LineStarts.Add(pos + 1);
}
pos = FullContent.IndexOf('\n', pos + 1);
}
if (LineEnds.Count < LineStarts.Count)
{
LineEnds.Add(FullContent.Length - 1);
}
prefix = languages.GetCommentPrefix(Language);
suffix = languages.GetCommentSuffix(Language);
inline = languages.GetCommentInline(Language);
Languages = languages;
pos = FullContent.IndexOf('\n', pos + 1);
}
public Languages Languages { get; set; }
if (LineEnds.Count < LineStarts.Count) LineEnds.Add(FullContent.Length - 1);
private bool _triedToConstructJsonDocument;
private JsonDocument? _jsonDocument;
internal IEnumerable<(string, Boundary)> GetStringFromJsonPath(string Path)
{
if (!_triedToConstructJsonDocument)
prefix = languages.GetCommentPrefix(Language);
suffix = languages.GetCommentSuffix(Language);
inline = languages.GetCommentInline(Language);
Languages = languages;
}
public Languages Languages { get; set; }
/// <summary>
/// The full string of the TextContainer represents.
/// </summary>
public string FullContent { get; }
/// <summary>
/// The code language of the file
/// </summary>
public string Language { get; }
/// <summary>
/// One-indexed array of the character indexes of the ends of the lines in FullContent.
/// </summary>
public List<int> LineEnds { get; }
/// <summary>
/// One-indexed array of the character indexes of the starts of the lines in FullContent.
/// </summary>
public List<int> LineStarts { get; }
/// <summary>
/// A dictionary mapping character index in FullContent to if a specific character is commented. See IsCommented to
/// use.
/// </summary>
private ConcurrentDictionary<int, bool> CommentedStates { get; } = new();
internal IEnumerable<(string, Boundary)> GetStringFromJsonPath(string Path)
{
if (!_triedToConstructJsonDocument)
try
{
try
{
_triedToConstructJsonDocument = true;
_jsonDocument = JsonDocument.Parse(FullContent);
}
catch (Exception e)
{
_logger.LogError("Failed to parse as a JSON document: {0}", e.Message);
_jsonDocument = null;
}
_triedToConstructJsonDocument = true;
_jsonDocument = JsonDocument.Parse(FullContent);
}
catch (Exception e)
{
_logger.LogError("Failed to parse as a JSON document: {0}", e.Message);
_jsonDocument = null;
}
if (_jsonDocument is not null)
{
var selector = JsonSelector.Parse(Path);
IList<JsonElement> values = selector.Select(_jsonDocument.RootElement);
if (_jsonDocument is not null)
{
var selector = JsonSelector.Parse(Path);
var field = typeof(JsonElement).GetField("_idx", BindingFlags.NonPublic | BindingFlags.Instance);
if (field is null)
{
_logger.LogWarning("Failed to access _idx field of JsonElement.");
}
else
{
foreach (JsonElement ele in values)
var values = selector.Select(_jsonDocument.RootElement);
var field = typeof(JsonElement).GetField("_idx", BindingFlags.NonPublic | BindingFlags.Instance);
if (field is null)
_logger.LogWarning("Failed to access _idx field of JsonElement.");
else
foreach (var ele in values)
// Private access hack
// The idx field is the start of the JSON element, including markup that isn't directly part of the element itself
if (field.GetValue(ele) is int idx)
{
// Private access hack
// The idx field is the start of the JSON element, including markup that isn't directly part of the element itself
if (field.GetValue(ele) is int idx)
var eleString = ele.ToString();
if (eleString is { } denulledString)
{
var eleString = ele.ToString();
if (eleString is { } denulledString)
var location = new Boundary
{
var location = new Boundary()
{
// Adjust the index to the start of the actual element
Index = FullContent[idx..].IndexOf(denulledString, StringComparison.Ordinal) + idx,
Length = eleString.Length
};
yield return (eleString, location);
}
// Adjust the index to the start of the actual element
Index = FullContent[idx..].IndexOf(denulledString, StringComparison.Ordinal) + idx,
Length = eleString.Length
};
yield return (eleString, location);
}
}
}
}
}
private bool _triedToConstructXPathDocument;
private XPathDocument? _xmlDoc;
/// <summary>
/// If this file is a JSON, XML or YML file, returns the string contents of the specified path.
/// If the path does not exist, or the file is not JSON, XML or YML returns null.
/// </summary>
/// <param name="Path"></param>
/// <returns></returns>
internal IEnumerable<(string, Boundary)> GetStringFromXPath(string Path)
{
if (!_triedToConstructXPathDocument)
{
try
{
_triedToConstructXPathDocument = true;
_xmlDoc = new XPathDocument(new StringReader(FullContent));
}
catch (Exception e)
{
_logger.LogError("Failed to parse as an XML document: {0}", e.Message);
_xmlDoc = null;
}
}
if (_xmlDoc is not null)
{
var navigator = _xmlDoc.CreateNavigator();
var nodeIter = navigator.Select(Path);
int minIndex = 0;
while (nodeIter.MoveNext())
{
if (nodeIter.Current is not null)
{
// First we find the name
var nameIndex = FullContent[minIndex..].IndexOf(nodeIter.Current.Name);
// Then we grab the index of the end of this tag.
// We can't use OuterXML because the parser will inject the namespace if present into the OuterXML so it doesn't match the original text.
var endTagIndex = FullContent[nameIndex..].IndexOf('>');
var totalOffset = nameIndex + endTagIndex + minIndex;
var offset = FullContent[totalOffset..].IndexOf(nodeIter.Current.InnerXml) + totalOffset;
// Move the minimum index up in case there are multiple instances of identical OuterXML
// This ensures we won't re-find the same one
minIndex = offset;
var location = new Boundary()
{
Index = offset,
Length = nodeIter.Current.InnerXml.Length
};
yield return (nodeIter.Current.Value, location);
}
}
}
}
/// <summary>
/// The full string of the TextContainer represents.
/// </summary>
public string FullContent { get; }
/// <summary>
/// The code language of the file
/// </summary>
public string Language { get; }
/// <summary>
/// One-indexed array of the character indexes of the ends of the lines in FullContent.
/// </summary>
public List<int> LineEnds { get; }
/// <summary>
/// One-indexed array of the character indexes of the starts of the lines in FullContent.
/// </summary>
public List<int> LineStarts { get; }
}
/// <summary>
/// A dictionary mapping character index in FullContent to if a specific character is commented. See IsCommented to use.
/// </summary>
private ConcurrentDictionary<int,bool> CommentedStates { get; } = new();
/// <summary>
/// Populates the CommentedStates Dictionary based on the index and the provided comment prefix and suffix
/// </summary>
/// <param name="index">The character index in FullContent</param>
/// <param name="prefix">The comment prefix</param>
/// <param name="suffix">The comment suffix</param>
private void PopulateCommentedStatesInternal(int index, string prefix, string suffix)
{
var prefixLoc = FullContent.LastIndexOf(prefix, index, StringComparison.Ordinal);
if (prefixLoc != -1)
/// <summary>
/// If this file is a JSON, XML or YML file, returns the string contents of the specified path.
/// If the path does not exist, or the file is not JSON, XML or YML returns null.
/// </summary>
/// <param name="Path"></param>
/// <returns></returns>
internal IEnumerable<(string, Boundary)> GetStringFromXPath(string Path)
{
if (!_triedToConstructXPathDocument)
try
{
if (!CommentedStates.ContainsKey(prefixLoc))
_triedToConstructXPathDocument = true;
_xmlDoc = new XPathDocument(new StringReader(FullContent));
}
catch (Exception e)
{
_logger.LogError("Failed to parse as an XML document: {0}", e.Message);
_xmlDoc = null;
}
if (_xmlDoc is not null)
{
var navigator = _xmlDoc.CreateNavigator();
var nodeIter = navigator.Select(Path);
var minIndex = 0;
while (nodeIter.MoveNext())
if (nodeIter.Current is not null)
{
var suffixLoc = FullContent.IndexOf(suffix, prefixLoc, StringComparison.Ordinal);
if (suffixLoc == -1)
// First we find the name
var nameIndex = FullContent[minIndex..].IndexOf(nodeIter.Current.Name);
// Then we grab the index of the end of this tag.
// We can't use OuterXML because the parser will inject the namespace if present into the OuterXML so it doesn't match the original text.
var endTagIndex = FullContent[nameIndex..].IndexOf('>');
var totalOffset = nameIndex + endTagIndex + minIndex;
var offset = FullContent[totalOffset..].IndexOf(nodeIter.Current.InnerXml) + totalOffset;
// Move the minimum index up in case there are multiple instances of identical OuterXML
// This ensures we won't re-find the same one
minIndex = offset;
var location = new Boundary
{
suffixLoc = FullContent.Length - 1;
}
for (int i = prefixLoc; i <= suffixLoc; i++)
{
CommentedStates[i] = true;
}
}
}
}
/// <summary>
/// Populate the CommentedStates Dictionary based on the provided index.
/// </summary>
/// <param name="index">The character index in FullContent to work based on.</param>
public void PopulateCommentedState(int index)
{
var inIndex = index;
if (index >= FullContent.Length)
{
index = FullContent.Length - 1;
}
if (index < 0)
{
index = 0;
}
// Populate true for the indexes of the most immediately preceding instance of the multiline comment type if found
if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix))
{
PopulateCommentedStatesInternal(index, prefix, suffix);
}
// Populate true for indexes of the most immediately preceding instance of the single-line comment type if found
if (!CommentedStates.ContainsKey(index) && !string.IsNullOrEmpty(inline))
{
PopulateCommentedStatesInternal(index, inline, "\n");
}
var i = index;
// Everything preceding this, including this, which doesn't have a commented state is
// therefore not commented so we backfill
while (!CommentedStates.ContainsKey(i) && i >= 0)
{
CommentedStates[i--] = false;
}
if (inIndex != index)
{
CommentedStates[inIndex] = CommentedStates[index];
}
}
/// <summary>
/// Gets the text for a given boundary
/// </summary>
/// <param name="boundary">The boundary to get text for.</param>
/// <returns></returns>
public string GetBoundaryText(Boundary boundary)
{
if (boundary is null)
{
return string.Empty;
}
var start = Math.Max(boundary.Index, 0);
var end = start + boundary.Length;
start = Math.Min(FullContent.Length, start);
end = Math.Min(FullContent.Length, end);
return FullContent[start..end];
}
/// <summary>
/// Returns boundary for a given index in text
/// </summary>
/// <param name="index"> Position in text </param>
/// <returns> Boundary </returns>
public Boundary GetLineBoundary(int index)
{
Boundary result = new();
for (int i = 0; i < LineEnds.Count; i++)
{
if (LineEnds[i] >= index)
{
result.Index = LineStarts[i];
result.Length = LineEnds[i] - LineStarts[i] + 1;
break;
}
}
return result;
}
/// <summary>
/// Return content of the line
/// </summary>
/// <param name="line"> Line number (one-indexed) </param>
/// <returns> Text </returns>
public string GetLineContent(int line)
{
if (line >= LineEnds.Count)
{
line = LineEnds.Count - 1;
}
int index = LineEnds[line];
Boundary bound = GetLineBoundary(index);
return FullContent.Substring(bound.Index, bound.Length);
}
/// <summary>
/// Returns location (Line, Column) for given index in text
/// </summary>
/// <param name="index"> Position in text (line is one-indexed)</param>
/// <returns> Location </returns>
public Location GetLocation(int index)
{
for (int i = 1; i < LineEnds.Count; i++)
{
if (LineEnds[i] >= index)
{
return new()
{
Column = index - LineStarts[i],
Line = i
Index = offset,
Length = nodeIter.Current.InnerXml.Length
};
yield return (nodeIter.Current.Value, location);
}
}
return new();
}
}
public bool IsCommented(int index)
{
if (!CommentedStates.ContainsKey(index))
/// <summary>
/// Populates the CommentedStates Dictionary based on the index and the provided comment prefix and suffix
/// </summary>
/// <param name="index">The character index in FullContent</param>
/// <param name="prefix">The comment prefix</param>
/// <param name="suffix">The comment suffix</param>
private void PopulateCommentedStatesInternal(int index, string prefix, string suffix)
{
var prefixLoc = FullContent.LastIndexOf(prefix, index, StringComparison.Ordinal);
if (prefixLoc != -1)
if (!CommentedStates.ContainsKey(prefixLoc))
{
PopulateCommentedState(index);
var suffixLoc = FullContent.IndexOf(suffix, prefixLoc, StringComparison.Ordinal);
if (suffixLoc == -1) suffixLoc = FullContent.Length - 1;
for (var i = prefixLoc; i <= suffixLoc; i++) CommentedStates[i] = true;
}
return CommentedStates[index];
}
}
/// <summary>
/// Check whether the boundary in a text matches the scope of a search pattern (code, comment etc.)
/// </summary>
/// <param name="pattern"> The scopes to check </param>
/// <param name="boundary"> Boundary in the text </param>
/// <param name="text"> Text </param>
/// <returns> True if boundary is in a provided scope </returns>
public bool ScopeMatch(IEnumerable<PatternScope> scopes, Boundary boundary)
{
if (scopes is null || !scopes.Any() || scopes.Contains(PatternScope.All))
/// <summary>
/// Populate the CommentedStates Dictionary based on the provided index.
/// </summary>
/// <param name="index">The character index in FullContent to work based on.</param>
public void PopulateCommentedState(int index)
{
var inIndex = index;
if (index >= FullContent.Length) index = FullContent.Length - 1;
if (index < 0) index = 0;
// Populate true for the indexes of the most immediately preceding instance of the multiline comment type if found
if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix))
PopulateCommentedStatesInternal(index, prefix, suffix);
// Populate true for indexes of the most immediately preceding instance of the single-line comment type if found
if (!CommentedStates.ContainsKey(index) && !string.IsNullOrEmpty(inline))
PopulateCommentedStatesInternal(index, inline, "\n");
var i = index;
// Everything preceding this, including this, which doesn't have a commented state is
// therefore not commented so we backfill
while (!CommentedStates.ContainsKey(i) && i >= 0) CommentedStates[i--] = false;
if (inIndex != index) CommentedStates[inIndex] = CommentedStates[index];
}
/// <summary>
/// Gets the text for a given boundary
/// </summary>
/// <param name="boundary">The boundary to get text for.</param>
/// <returns></returns>
public string GetBoundaryText(Boundary boundary)
{
if (boundary is null) return string.Empty;
var start = Math.Max(boundary.Index, 0);
var end = start + boundary.Length;
start = Math.Min(FullContent.Length, start);
end = Math.Min(FullContent.Length, end);
return FullContent[start..end];
}
/// <summary>
/// Returns boundary for a given index in text
/// </summary>
/// <param name="index"> Position in text </param>
/// <returns> Boundary </returns>
public Boundary GetLineBoundary(int index)
{
Boundary result = new();
for (var i = 0; i < LineEnds.Count; i++)
if (LineEnds[i] >= index)
{
return true;
result.Index = LineStarts[i];
result.Length = LineEnds[i] - LineStarts[i] + 1;
break;
}
if (scopes.Contains(PatternScope.All) || string.IsNullOrEmpty(prefix))
return true;
bool isInComment = IsCommented(boundary.Index);
return (!isInComment && scopes.Contains(PatternScope.Code)) || (isInComment && scopes.Contains(PatternScope.Comment));
}
return result;
}
private readonly string inline;
private readonly string prefix;
private readonly string suffix;
private readonly ILogger _logger;
/// <summary>
/// Return content of the line
/// </summary>
/// <param name="line"> Line number (one-indexed) </param>
/// <returns> Text </returns>
public string GetLineContent(int line)
{
if (line >= LineEnds.Count) line = LineEnds.Count - 1;
var index = LineEnds[line];
var bound = GetLineBoundary(index);
return FullContent.Substring(bound.Index, bound.Length);
}
/// <summary>
/// Returns location (Line, Column) for given index in text
/// </summary>
/// <param name="index"> Position in text (line is one-indexed)</param>
/// <returns> Location </returns>
public Location GetLocation(int index)
{
for (var i = 1; i < LineEnds.Count; i++)
if (LineEnds[i] >= index)
return new Location
{
Column = index - LineStarts[i],
Line = i
};
return new Location();
}
public bool IsCommented(int index)
{
if (!CommentedStates.ContainsKey(index)) PopulateCommentedState(index);
return CommentedStates[index];
}
/// <summary>
/// Check whether the boundary in a text matches the scope of a search pattern (code, comment etc.)
/// </summary>
/// <param name="pattern"> The scopes to check </param>
/// <param name="boundary"> Boundary in the text </param>
/// <param name="text"> Text </param>
/// <returns> True if boundary is in a provided scope </returns>
public bool ScopeMatch(IEnumerable<PatternScope> scopes, Boundary boundary)
{
if (scopes is null || !scopes.Any() || scopes.Contains(PatternScope.All)) return true;
if (scopes.Contains(PatternScope.All) || string.IsNullOrEmpty(prefix))
return true;
var isInComment = IsCommented(boundary.Index);
return (!isInComment && scopes.Contains(PatternScope.Code)) ||
(isInComment && scopes.Contains(PatternScope.Comment));
}
}

Просмотреть файл

@ -9,10 +9,11 @@ using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.RulesEngine;
/// <summary>
/// The TypedRuleSet allows you to extend the Application Inspector Rule format with your own custom fields and have them be deserialized. They won't be used in processing, but may be used for additional follow up actions.
/// The TypedRuleSet allows you to extend the Application Inspector Rule format with your own custom fields and have
/// them be deserialized. They won't be used in processing, but may be used for additional follow up actions.
/// </summary>
/// <typeparam name="T">The Type of the Rule this set holds. It must inherit from <see cref="Rule"/></typeparam>
public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
/// <typeparam name="T">The Type of the Rule this set holds. It must inherit from <see cref="Rule" /></typeparam>
public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
{
/// <summary>
/// Creates instance of TypedRuleSet
@ -23,33 +24,35 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
}
/// <summary>
/// Returns an enumerator that iterates through the AI Formatted <see cref="Rule"/>.
/// Returns an enumerator that iterates through the AI Formatted <see cref="Rule" />.
/// </summary>
/// <returns> Enumerator </returns>
IEnumerator<T> IEnumerable<T>.GetEnumerator() => AppInspectorRulesAsEnumerableT().GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through the AI Formatted <see cref="Rule"/>.
/// </summary>
/// <returns> Enumerator </returns>
public IEnumerator GetEnumerator() => AppInspectorRulesAsEnumerableT().GetEnumerator();
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return AppInspectorRulesAsEnumerableT().GetEnumerator();
}
/// <summary>
/// Returns the set of rules as an <see cref="IEnumerable{T}"/>
/// Returns an enumerator that iterates through the AI Formatted <see cref="Rule" />.
/// </summary>
/// <returns> Enumerator </returns>
public IEnumerator GetEnumerator()
{
return AppInspectorRulesAsEnumerableT().GetEnumerator();
}
/// <summary>
/// Returns the set of rules as an <see cref="IEnumerable{T}" />
/// </summary>
/// <returns></returns>
private IEnumerable<T> AppInspectorRulesAsEnumerableT()
{
foreach (var rule in _rules)
{
if (rule is T ruleAsT)
{
yield return ruleAsT;
}
}
}
/// <summary>
/// Load rules from a file or directory
/// </summary>
@ -57,21 +60,18 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
/// <param name="tag"> Tag for the rules </param>
/// <exception cref="ArgumentException">Thrown if the filename is null or empty</exception>
/// <exception cref="FileNotFoundException">Thrown if the specified file cannot be found on the file system</exception>
/// <exception cref="Newtonsoft.Json.JsonSerializationException">Thrown if the specified file cannot be deserialized as a <see cref="List{T}"/></exception>
/// <exception cref="Newtonsoft.Json.JsonSerializationException">
/// Thrown if the specified file cannot be deserialized as a
/// <see cref="List{T}" />
/// </exception>
public void AddPath(string path, string? tag = null)
{
if (Directory.Exists(path))
{
AddDirectory(path, tag);
}
else if (File.Exists(path))
{
AddFile(path, tag);
}
else
{
throw new ArgumentException("The path must exist.", nameof(path));
}
}
/// <summary>
@ -81,16 +81,17 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
/// <param name="tag"> Tag for the rules </param>
/// <exception cref="ArgumentException">Thrown if the filename is null or empty</exception>
/// <exception cref="FileNotFoundException">Thrown if the specified file cannot be found on the file system</exception>
/// <exception cref="Newtonsoft.Json.JsonSerializationException">Thrown if the specified file cannot be deserialized as a <see cref="List{T}"/></exception>
/// <exception cref="Newtonsoft.Json.JsonSerializationException">
/// Thrown if the specified file cannot be deserialized as a
/// <see cref="List{T}" />
/// </exception>
public void AddDirectory(string path, string? tag = null)
{
if (!Directory.Exists(path))
throw new DirectoryNotFoundException();
foreach (string filename in Directory.EnumerateFileSystemEntries(path, "*.json", SearchOption.AllDirectories))
{
foreach (var filename in Directory.EnumerateFileSystemEntries(path, "*.json", SearchOption.AllDirectories))
AddFile(filename, tag);
}
}
/// <summary>
@ -100,7 +101,10 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
/// <param name="tag"> Tag for the rules </param>
/// <exception cref="ArgumentException">Thrown if the filename is null or empty</exception>
/// <exception cref="FileNotFoundException">Thrown if the specified file cannot be found on the file system</exception>
/// <exception cref="Newtonsoft.Json.JsonSerializationException">Thrown if the specified file cannot be deserialized as a <see cref="List{T}"/></exception>
/// <exception cref="Newtonsoft.Json.JsonSerializationException">
/// Thrown if the specified file cannot be deserialized as a
/// <see cref="List{T}" />
/// </exception>
public void AddFile(string? filename, string? tag = null)
{
if (string.IsNullOrEmpty(filename))
@ -109,7 +113,7 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
if (!File.Exists(filename))
throw new FileNotFoundException();
using StreamReader file = File.OpenText(filename);
using var file = File.OpenText(filename);
AddString(file.ReadToEnd(), filename, tag);
}
@ -123,21 +127,16 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
public void AddString(string jsonString, string sourceName, string? tag = null)
{
if (StringToRules(jsonString ?? string.Empty, sourceName ?? string.Empty, tag) is { } deserializedList)
{
AddRange(deserializedList);
}
}
/// <summary>
/// Adds the elements of the collection to the Ruleset
/// </summary>
/// <param name="collection"> Collection of rules </param>
public void AddRange(IEnumerable<T>? collection)
{
foreach (T rule in collection ?? Array.Empty<T>())
{
AddRule(rule);
}
foreach (var rule in collection ?? Array.Empty<T>()) AddRule(rule);
}
/// <summary>
@ -153,12 +152,14 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
}
else
{
_logger.LogError("Rule '{RuleId}:{RuleName}' could not be converted into an OAT rule. There may be message in the logs indicating why. You can run rule verification to identify the issue", rule.Id, rule.Name);
_logger.LogError(
"Rule '{RuleId}:{RuleName}' could not be converted into an OAT rule. There may be message in the logs indicating why. You can run rule verification to identify the issue",
rule.Id, rule.Name);
}
}
/// <summary>
/// Deserialize a string into rules and enumerate them.
/// Deserialize a string into rules and enumerate them.
/// </summary>
/// <param name="jsonString"></param>
/// <param name="sourceName"></param>
@ -173,17 +174,17 @@ public class TypedRuleSet<T> : AbstractRuleSet, IEnumerable<T> where T : Rule
}
catch (JsonSerializationException jsonSerializationException)
{
_logger.LogError("Failed to deserialize '{0}' at Line {1} Column {2}", sourceName, jsonSerializationException.LineNumber, jsonSerializationException.LinePosition);
_logger.LogError("Failed to deserialize '{0}' at Line {1} Column {2}", sourceName,
jsonSerializationException.LineNumber, jsonSerializationException.LinePosition);
throw;
}
if (ruleList is not null)
{
foreach (T r in ruleList)
foreach (var r in ruleList)
{
r.Source = sourceName;
r.RuntimeTag = tag;
yield return r;
}
}
}
}

Просмотреть файл

@ -1,34 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="output\**" />
<EmbeddedResource Remove="output\**" />
<None Remove="output\**" />
<None Update="TestData\FourWindowsOneLinux.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Remove="output\**" />
<EmbeddedResource Remove="output\**" />
<None Remove="output\**" />
<None Update="TestData\FourWindowsOneLinux.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
<ItemGroup>
<ProjectReference Include="..\AppInspector.Logging\AppInspector.Logging.csproj" />
<ProjectReference Include="..\AppInspector\AppInspector.Commands.csproj" />
</ItemGroup>
</Project>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Logging\AppInspector.Logging.csproj" />
<ProjectReference Include="..\AppInspector\AppInspector.Commands.csproj" />
</ItemGroup>
</Project>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -7,33 +7,15 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.Commands
namespace AppInspector.Tests.Commands;
[TestClass]
[ExcludeFromCodeCoverage]
public class TestExportTagsCmd
{
[TestClass]
[ExcludeFromCodeCoverage]
public class TestExportTagsCmd
{
private string testRulesPath = string.Empty;
private ILoggerFactory factory = new NullLoggerFactory();
private LogOptions logOptions = new();
private ILoggerFactory factory = new NullLoggerFactory();
[TestInitialize]
public void InitOutput()
{
factory = logOptions.GetLoggerFactory();
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
testRulesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestRules.json");
File.WriteAllText(testRulesPath, findWindows);
}
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
string findWindows = @"[
private readonly string findWindows = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
@ -112,42 +94,59 @@ namespace AppInspector.Tests.Commands
}
]";
[TestMethod]
public void ExportCustom()
{
ExportTagsOptions options = new()
{
IgnoreDefaultRules = true,
CustomRulesPath = testRulesPath
};
ExportTagsCommand command = new(options, factory);
ExportTagsResult result = command.GetResult();
Assert.IsTrue(result.TagsList.Contains("Test.Tags.Linux"));
Assert.IsTrue(result.TagsList.Contains("Test.Tags.Windows"));
Assert.AreEqual(2, result.TagsList.Count);
Assert.AreEqual(ExportTagsResult.ExitCode.Success, result.ResultCode);
}
private readonly LogOptions logOptions = new();
private string testRulesPath = string.Empty;
[TestMethod]
public void ExportDefault()
{
ExportTagsOptions options = new()
{
IgnoreDefaultRules = false
};
ExportTagsCommand command = new(options, factory);
ExportTagsResult result = command.GetResult();
Assert.AreEqual(ExportTagsResult.ExitCode.Success, result.ResultCode);
}
[TestInitialize]
public void InitOutput()
{
factory = logOptions.GetLoggerFactory();
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
testRulesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestRules.json");
File.WriteAllText(testRulesPath, findWindows);
}
[TestMethod]
public void NoDefaultNoCustomRules()
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
[TestMethod]
public void ExportCustom()
{
ExportTagsOptions options = new()
{
ExportTagsOptions options = new()
{
IgnoreDefaultRules = true
};
Assert.ThrowsException<OpException>(() => new ExportTagsCommand(options));
}
IgnoreDefaultRules = true,
CustomRulesPath = testRulesPath
};
ExportTagsCommand command = new(options, factory);
var result = command.GetResult();
Assert.IsTrue(result.TagsList.Contains("Test.Tags.Linux"));
Assert.IsTrue(result.TagsList.Contains("Test.Tags.Windows"));
Assert.AreEqual(2, result.TagsList.Count);
Assert.AreEqual(ExportTagsResult.ExitCode.Success, result.ResultCode);
}
[TestMethod]
public void ExportDefault()
{
ExportTagsOptions options = new()
{
IgnoreDefaultRules = false
};
ExportTagsCommand command = new(options, factory);
var result = command.GetResult();
Assert.AreEqual(ExportTagsResult.ExitCode.Success, result.ResultCode);
}
[TestMethod]
public void NoDefaultNoCustomRules()
{
ExportTagsOptions options = new()
{
IgnoreDefaultRules = true
};
Assert.ThrowsException<OpException>(() => new ExportTagsCommand(options));
}
}

Просмотреть файл

@ -4,43 +4,42 @@ using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.Commands
namespace AppInspector.Tests.Commands;
[TestClass]
[ExcludeFromCodeCoverage]
public class TestPackRulesCmd
{
[TestClass]
[ExcludeFromCodeCoverage]
public class TestPackRulesCmd
[TestInitialize]
public void InitOutput()
{
[TestInitialize]
public void InitOutput()
{
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
}
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
}
[TestCleanup]
public void CleanUp()
[TestCleanup]
public void CleanUp()
{
try
{
try
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
catch
{
}
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
[TestMethod]
public void NoCustomNoEmbeddedRules()
catch
{
Assert.ThrowsException<OpException>(() => new PackRulesCommand(new()));
}
[TestMethod]
public void PackEmbeddedRules()
{
PackRulesOptions options = new() { PackEmbeddedRules = true };
PackRulesCommand command = new(options);
PackRulesResult result = command.GetResult();
Assert.AreEqual(PackRulesResult.ExitCode.Success, result.ResultCode);
}
}
[TestMethod]
public void NoCustomNoEmbeddedRules()
{
Assert.ThrowsException<OpException>(() => new PackRulesCommand(new PackRulesOptions()));
}
[TestMethod]
public void PackEmbeddedRules()
{
PackRulesOptions options = new() { PackEmbeddedRules = true };
PackRulesCommand command = new(options);
var result = command.GetResult();
Assert.AreEqual(PackRulesResult.ExitCode.Success, result.ResultCode);
}
}

Просмотреть файл

@ -8,49 +8,17 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.Commands
namespace AppInspector.Tests.Commands;
/// <summary>
/// Test class for TagDiff Command
/// </summary>
[TestClass]
[ExcludeFromCodeCoverage]
public class TestTagDiffCmd
{
/// <summary>
/// Test class for TagDiff Command
/// </summary>
[TestClass]
[ExcludeFromCodeCoverage]
public class TestTagDiffCmd
{
private string testFileFourWindowsOneLinuxPath = string.Empty;
private string testFileFourWindowsOneLinuxCopyPath = string.Empty;
private string testFileFourWindowsNoLinuxPath = string.Empty;
private string testRulesPath = string.Empty;
private LogOptions logOptions = new();
private ILoggerFactory loggerFactory = new NullLoggerFactory();
[TestInitialize]
public void InitOutput()
{
loggerFactory = logOptions.GetLoggerFactory();
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
testFileFourWindowsOneLinuxPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestFile.js");
File.WriteAllText(testFileFourWindowsOneLinuxPath, fourWindowsOneLinux);
testFileFourWindowsOneLinuxCopyPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestFileCopy.js");
File.WriteAllText(testFileFourWindowsOneLinuxCopyPath, fourWindowsOneLinux);
testFileFourWindowsNoLinuxPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestFileNoLinux.js");
File.WriteAllText(testFileFourWindowsNoLinuxPath, fourWindowsNoLinux);
testRulesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestRules.json");
File.WriteAllText(testRulesPath, findWindows);
}
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
// These simple test rules rules look for the string "windows" and "linux"
string findWindows = @"[
// These simple test rules rules look for the string "windows" and "linux"
private readonly string findWindows = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
@ -90,89 +58,125 @@ namespace AppInspector.Tests.Commands
]
}
]";
// This string contains windows four times and linux once.
string fourWindowsOneLinux =
@"windows
private readonly string fourWindowsNoLinux =
@"windows
windows
windows
windows
";
// This string contains windows four times and linux once.
private readonly string fourWindowsOneLinux =
@"windows
windows
linux
windows
windows
";
string fourWindowsNoLinux =
@"windows
windows
windows
windows
";
[DataRow(TagTestType.Equality, TagDiffResult.ExitCode.TestPassed)]
[DataRow(TagTestType.Inequality, TagDiffResult.ExitCode.TestFailed)]
[TestMethod]
public void Equality(TagTestType tagTestType, TagDiffResult.ExitCode expectedExitCode)
private ILoggerFactory loggerFactory = new NullLoggerFactory();
private readonly LogOptions logOptions = new();
private string testFileFourWindowsNoLinuxPath = string.Empty;
private string testFileFourWindowsOneLinuxCopyPath = string.Empty;
private string testFileFourWindowsOneLinuxPath = string.Empty;
private string testRulesPath = string.Empty;
[TestInitialize]
public void InitOutput()
{
loggerFactory = logOptions.GetLoggerFactory();
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
testFileFourWindowsOneLinuxPath =
Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestFile.js");
File.WriteAllText(testFileFourWindowsOneLinuxPath, fourWindowsOneLinux);
testFileFourWindowsOneLinuxCopyPath =
Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestFileCopy.js");
File.WriteAllText(testFileFourWindowsOneLinuxCopyPath, fourWindowsOneLinux);
testFileFourWindowsNoLinuxPath =
Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestFileNoLinux.js");
File.WriteAllText(testFileFourWindowsNoLinuxPath, fourWindowsNoLinux);
testRulesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestRules.json");
File.WriteAllText(testRulesPath, findWindows);
}
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
[DataRow(TagTestType.Equality, TagDiffResult.ExitCode.TestPassed)]
[DataRow(TagTestType.Inequality, TagDiffResult.ExitCode.TestFailed)]
[TestMethod]
public void Equality(TagTestType tagTestType, TagDiffResult.ExitCode expectedExitCode)
{
TagDiffOptions options = new()
{
TagDiffOptions options = new()
{
SourcePath1 = new[] { testFileFourWindowsOneLinuxPath },
SourcePath2 = new[] { testFileFourWindowsOneLinuxCopyPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
IgnoreDefaultRules = true,
TestType = tagTestType,
CustomRulesPath = testRulesPath
};
SourcePath1 = new[] { testFileFourWindowsOneLinuxPath },
SourcePath2 = new[] { testFileFourWindowsOneLinuxCopyPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
IgnoreDefaultRules = true,
TestType = tagTestType,
CustomRulesPath = testRulesPath
};
TagDiffCommand command = new(options, loggerFactory);
TagDiffResult result = command.GetResult();
TagDiffCommand command = new(options, loggerFactory);
var result = command.GetResult();
Assert.AreEqual(expectedExitCode, result.ResultCode);
}
Assert.AreEqual(expectedExitCode, result.ResultCode);
}
[DataRow(TagTestType.Equality, TagDiffResult.ExitCode.TestFailed)]
[DataRow(TagTestType.Inequality, TagDiffResult.ExitCode.TestPassed)]
[TestMethod]
public void Inequality(TagTestType tagTestType, TagDiffResult.ExitCode expectedExitCode)
[DataRow(TagTestType.Equality, TagDiffResult.ExitCode.TestFailed)]
[DataRow(TagTestType.Inequality, TagDiffResult.ExitCode.TestPassed)]
[TestMethod]
public void Inequality(TagTestType tagTestType, TagDiffResult.ExitCode expectedExitCode)
{
TagDiffOptions options = new()
{
TagDiffOptions options = new()
{
SourcePath1 = new[] { testFileFourWindowsOneLinuxPath },
SourcePath2 = new[] { testFileFourWindowsNoLinuxPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
IgnoreDefaultRules = true,
TestType = tagTestType,
CustomRulesPath = testRulesPath
};
SourcePath1 = new[] { testFileFourWindowsOneLinuxPath },
SourcePath2 = new[] { testFileFourWindowsNoLinuxPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
IgnoreDefaultRules = true,
TestType = tagTestType,
CustomRulesPath = testRulesPath
};
TagDiffCommand command = new(options, loggerFactory);
TagDiffResult result = command.GetResult();
TagDiffCommand command = new(options, loggerFactory);
var result = command.GetResult();
Assert.AreEqual(expectedExitCode, result.ResultCode);
}
Assert.AreEqual(expectedExitCode, result.ResultCode);
}
[TestMethod]
public void InvalidSourcePath_Fail()
[TestMethod]
public void InvalidSourcePath_Fail()
{
TagDiffOptions options = new()
{
TagDiffOptions options = new()
{
SourcePath1 = new[] { $"{testFileFourWindowsOneLinuxPath}.not.a.path" },
SourcePath2 = new[] { testFileFourWindowsOneLinuxPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
};
var cmd = new TagDiffCommand(options, loggerFactory);
Assert.ThrowsException<OpException>(() => cmd.GetResult());
}
SourcePath1 = new[] { $"{testFileFourWindowsOneLinuxPath}.not.a.path" },
SourcePath2 = new[] { testFileFourWindowsOneLinuxPath },
FilePathExclusions = Array.Empty<string>() //allow source under unittest path
};
var cmd = new TagDiffCommand(options, loggerFactory);
Assert.ThrowsException<OpException>(() => cmd.GetResult());
}
[TestMethod]
public void NoDefaultNoCustomRules_Fail()
[TestMethod]
public void NoDefaultNoCustomRules_Fail()
{
TagDiffOptions options = new()
{
TagDiffOptions options = new()
{
SourcePath1 = new[] { testFileFourWindowsOneLinuxPath },
SourcePath2 = new[] { testFileFourWindowsOneLinuxCopyPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
IgnoreDefaultRules = true
};
SourcePath1 = new[] { testFileFourWindowsOneLinuxPath },
SourcePath2 = new[] { testFileFourWindowsOneLinuxCopyPath },
FilePathExclusions = Array.Empty<string>(), //allow source under unittest path
IgnoreDefaultRules = true
};
TagDiffCommand command = new TagDiffCommand(options, loggerFactory);
Assert.ThrowsException<OpException>(() => command.GetResult());
}
var command = new TagDiffCommand(options, loggerFactory);
Assert.ThrowsException<OpException>(() => command.GetResult());
}
}

Просмотреть файл

@ -8,34 +8,247 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.Commands
namespace AppInspector.Tests.Commands;
// TODO: This does not intentionally try to make the OAT rule maker fail
// The OAT rules are being validated but there aren't test cases that intentionally try to break it.
[TestClass]
[ExcludeFromCodeCoverage]
public class TestVerifyRulesCmd
{
// TODO: This does not intentionally try to make the OAT rule maker fail
// The OAT rules are being validated but there aren't test cases that intentionally try to break it.
[TestClass]
[ExcludeFromCodeCoverage]
public class TestVerifyRulesCmd
{
private string _validRulesPath = string.Empty;
private LogOptions _logOptions = new();
private ILoggerFactory _factory = new NullLoggerFactory();
[TestInitialize]
public void InitOutput()
{
_factory = _logOptions.GetLoggerFactory();
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
_validRulesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestRules.json");
File.WriteAllText(_validRulesPath, _validRules);
}
// FileRegexes if specified must be valid
private readonly string _invalidFileRegexes = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to_file_regex"": [ ""$(^""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}
]";
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
// Rules are a List<Rule> so they must be contained in []
private readonly string _invalidJsonValidRule = @"
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}";
readonly string _validRules = @"[
// Languages if specified must be known
private readonly string _knownLanguages = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""malboge""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}
]";
// MustMatch if specified must be matched
private readonly string _mustMatchRule = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-match"" : [ ""windows 2000""]
}
]";
// MustMatch if specified must not fail to match
private readonly string _mustMatchRuleFail = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-match"" : [ ""wimdoos""]
}
]";
// MustNotMatch if specified must not be matched
private readonly string _mustNotMatchRule = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-not-match"" : [ ""linux""]
}
]";
// MustNotMatch if specified must not fail to not be matched
private readonly string _mustNotMatchRuleFail = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-not-match"" : [ ""windows""]
}
]";
// Two rules may not have the same id
private readonly string _sameId = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
},
{
""name"": ""Platform: Linux"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'linux'"",
""tags"": [
""Test.Tags.Linux""
],
""severity"": ""Moderate"",
""patterns"": [
{
""confidence"": ""High"",
""modifiers"": [
""i""
],
""pattern"": ""linux"",
""type"": ""String"",
}
]
}
]";
// Rules must contain an id
private readonly string _validJsonInvalidRuleNoId = @"[{
""name"": ""Platform: Microsoft Windows"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}]";
private readonly string _validRules = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
@ -74,414 +287,205 @@ namespace AppInspector.Tests.Commands
}
]
}
]";
// Rules are a List<Rule> so they must be contained in []
readonly string _invalidJsonValidRule = @"
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}";
// Rules must contain an id
readonly string _validJsonInvalidRuleNoId = @"[{
""name"": ""Platform: Microsoft Windows"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}]";
// Two rules may not have the same id
readonly string _sameId = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
},
{
""name"": ""Platform: Linux"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'linux'"",
""tags"": [
""Test.Tags.Linux""
],
""severity"": ""Moderate"",
""patterns"": [
{
""confidence"": ""High"",
""modifiers"": [
""i""
],
""pattern"": ""linux"",
""type"": ""String"",
}
]
}
]";
// Languages if specified must be known
readonly string _knownLanguages = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""malboge""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}
]";
// MustMatch if specified must be matched
readonly string _mustMatchRule = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-match"" : [ ""windows 2000""]
}
]";
// MustMatch if specified must not fail to match
readonly string _mustMatchRuleFail = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-match"" : [ ""wimdoos""]
}
]";
// MustNotMatch if specified must not be matched
readonly string _mustNotMatchRule = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-not-match"" : [ ""linux""]
}
]";
// MustNotMatch if specified must not fail to not be matched
readonly string _mustNotMatchRuleFail = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to"": [ ""csharp""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
],
""must-not-match"" : [ ""windows""]
}
]";
private ILoggerFactory _factory = new NullLoggerFactory();
private readonly LogOptions _logOptions = new();
private string _validRulesPath = string.Empty;
// FileRegexes if specified must be valid
readonly string _invalidFileRegexes = @"[
{
""name"": ""Platform: Microsoft Windows"",
""id"": ""AI_TEST_WINDOWS"",
""description"": ""This rule checks for the string 'windows'"",
""tags"": [
""Test.Tags.Windows""
],
""applies_to_file_regex"": [ ""$(^""],
""severity"": ""Important"",
""patterns"": [
{
""confidence"": ""Medium"",
""modifiers"": [
""i""
],
""pattern"": ""windows"",
""type"": ""String"",
}
]
}
]";
/// <summary>
/// Ensure an exception is thrown if you don't specify any rules to verify
/// </summary>
[TestMethod]
public void NoDefaultNoCustomRules()
[TestInitialize]
public void InitOutput()
{
_factory = _logOptions.GetLoggerFactory();
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
_validRulesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "TestRules.json");
File.WriteAllText(_validRulesPath, _validRules);
}
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
/// <summary>
/// Ensure an exception is thrown if you don't specify any rules to verify
/// </summary>
[TestMethod]
public void NoDefaultNoCustomRules()
{
Assert.ThrowsException<OpException>(() => new VerifyRulesCommand(new VerifyRulesOptions()));
}
[TestMethod]
public void CustomRules()
{
VerifyRulesOptions options = new()
{
Assert.ThrowsException<OpException>(() => new VerifyRulesCommand(new()));
}
CustomRulesPath = _validRulesPath
};
[TestMethod]
public void CustomRules()
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
}
[TestMethod]
public void UnclosedJson()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _invalidJsonValidRule);
VerifyRulesOptions options = new()
{
VerifyRulesOptions options = new()
{
CustomRulesPath = _validRulesPath,
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.CriticalError, result.ResultCode);
}
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
}
[TestMethod]
public void UnclosedJson()
[TestMethod]
public void NullId()
{
var set = new RuleSet();
set.AddString(_validJsonInvalidRuleNoId, "NoIdTest");
RulesVerifierOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _invalidJsonValidRule);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
LoggerFactory = _factory
};
var rulesVerifier = new RulesVerifier(options);
Assert.IsFalse(rulesVerifier.Verify(set).Verified);
}
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.CriticalError, result.ResultCode);
}
[TestMethod]
public void NullId()
[TestMethod]
public void DuplicateId()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _sameId);
VerifyRulesOptions options = new()
{
RuleSet set = new RuleSet();
set.AddString(_validJsonInvalidRuleNoId, "NoIdTest");
RulesVerifierOptions options = new()
{
LoggerFactory = _factory
};
RulesVerifier rulesVerifier = new RulesVerifier(options);
Assert.IsFalse(rulesVerifier.Verify(set).Verified);
}
CustomRulesPath = path
};
[TestMethod]
public void DuplicateId()
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
File.Delete(path);
}
[TestMethod]
public void DuplicateIdCheckDisabled()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _sameId);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _sameId);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
CustomRulesPath = path,
DisableRequireUniqueIds = true
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
File.Delete(path);
}
[TestMethod]
public void DuplicateIdCheckDisabled()
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
File.Delete(path);
}
[TestMethod]
public void InvalidRegex()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _invalidFileRegexes);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _sameId);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
DisableRequireUniqueIds = true
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
File.Delete(path);
}
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
[TestMethod]
public void InvalidRegex()
[TestMethod]
public void UnknownLanguage()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _knownLanguages);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _invalidFileRegexes);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
[TestMethod]
public void UnknownLanguage()
[TestMethod]
public void MustMatch()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _mustMatchRule);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _knownLanguages);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
[TestMethod]
public void MustMatch()
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
}
[TestMethod]
public void MustMatchDetectIncorrect()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _mustMatchRuleFail);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _mustMatchRule);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
}
[TestMethod]
public void MustMatchDetectIncorrect()
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
[TestMethod]
public void MustNotMatch()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _mustNotMatchRule);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _mustMatchRuleFail);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
[TestMethod]
public void MustNotMatch()
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
}
[TestMethod]
public void MustNotMatchDetectIncorrect()
{
var path = Path.GetTempFileName();
File.WriteAllText(path, _mustNotMatchRuleFail);
VerifyRulesOptions options = new()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _mustNotMatchRule);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
CustomRulesPath = path
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
}
[TestMethod]
public void MustNotMatchDetectIncorrect()
{
string path = Path.GetTempFileName();
File.WriteAllText(path, _mustNotMatchRuleFail);
VerifyRulesOptions options = new()
{
CustomRulesPath = path,
};
VerifyRulesCommand command = new(options, _factory);
VerifyRulesResult result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
VerifyRulesCommand command = new(options, _factory);
var result = command.GetResult();
File.Delete(path);
Assert.AreEqual(VerifyRulesResult.ExitCode.NotVerified, result.ResultCode);
}
}

Просмотреть файл

@ -1,5 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.ApplicationInspector.Commands;
using Microsoft.ApplicationInspector.Logging;
@ -8,79 +7,67 @@ using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Serilog.Events;
namespace AppInspector.Tests.DefaultRules
namespace AppInspector.Tests.DefaultRules;
/// <summary>
/// Tests for the default set of rules which are embedded in the executable.
/// </summary>
[TestClass]
[ExcludeFromCodeCoverage]
public class TestDefaultRules
{
/// <summary>
/// Tests for the default set of rules which are embedded in the executable.
/// </summary>
[TestClass]
[ExcludeFromCodeCoverage]
public class TestDefaultRules
// This test ensures that the rules that are bundled with Application Inspector are valid.
[TestMethod]
public void VerifyDefaultRulesAreValid()
{
// This test ensures that the rules that are bundled with Application Inspector are valid.
[TestMethod]
public void VerifyDefaultRules()
VerifyRulesOptions options = new()
{
VerifyRulesOptions options = new()
{
VerifyDefaultRules = true,
};
var loggerFactory = new LogOptions() {ConsoleVerbosityLevel = LogEventLevel.Debug}.GetLoggerFactory();
var logger = loggerFactory.CreateLogger("Tests");
VerifyRulesCommand command = new(options, loggerFactory);
VerifyRulesResult result = command.GetResult();
if (result.Unverified.Any())
{
logger.Log(LogLevel.Error, "{0} of {1} rules failed verification. Errors are as follows:", result.Unverified.Count(), result.RuleStatusList.Count);
}
else
{
logger.Log(LogLevel.Information, "All {0} rules passed validation.", result.RuleStatusList.Count);
}
VerifyDefaultRules = true
};
var loggerFactory = new LogOptions { ConsoleVerbosityLevel = LogEventLevel.Debug }.GetLoggerFactory();
var logger = loggerFactory.CreateLogger("Tests");
VerifyRulesCommand command = new(options, loggerFactory);
var result = command.GetResult();
foreach (var unverified in result.Unverified)
{
logger.Log(LogLevel.Error, "Failed to validate {0}",unverified.RulesId);
foreach (var error in unverified.Errors)
{
logger.Log(LogLevel.Error, error);
}
if (result.Unverified.Any())
logger.Log(LogLevel.Error, "{0} of {1} rules failed verification. Errors are as follows:",
result.Unverified.Count(), result.RuleStatusList.Count);
else
logger.Log(LogLevel.Information, "All {0} rules passed validation.", result.RuleStatusList.Count);
foreach (var oatError in unverified.OatIssues)
{
logger.Log(LogLevel.Error, oatError.Description);
}
}
logger.Log(LogLevel.Information, "{0} of {1} rules have positive self tests.",result.RuleStatusList.Count(x => x.HasPositiveSelfTests),result.RuleStatusList.Count);
logger.Log(LogLevel.Information, "{0} of {1} rules have negative self tests.",result.RuleStatusList.Count(x => x.HasNegativeSelfTests),result.RuleStatusList.Count);
foreach (var unverified in result.Unverified)
{
logger.Log(LogLevel.Error, "Failed to validate {0}", unverified.RulesId);
foreach (var error in unverified.Errors) logger.Log(LogLevel.Error, error);
foreach (var rule in result.RuleStatusList.Where(x => !x.HasPositiveSelfTests))
{
logger.Log(LogLevel.Warning, "Rule {0} does not have any positive test cases", rule.RulesId);
}
foreach (var rule in result.RuleStatusList.Where(x => !x.HasNegativeSelfTests))
{
logger.Log(LogLevel.Warning, "Rule {0} does not have any negative test cases", rule.RulesId);
}
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
Assert.AreNotEqual(0, result.RuleStatusList.Count);
foreach (var oatError in unverified.OatIssues) logger.Log(LogLevel.Error, oatError.Description);
}
[TestMethod]
public void VerifyNonZeroDefaultRules()
{
RuleSet set = RuleSetUtils.GetDefaultRuleSet();
Assert.IsTrue(set.Any());
logger.Log(LogLevel.Information, "{0} of {1} rules have positive self tests.",
result.RuleStatusList.Count(x => x.HasPositiveSelfTests), result.RuleStatusList.Count);
logger.Log(LogLevel.Information, "{0} of {1} rules have negative self tests.",
result.RuleStatusList.Count(x => x.HasNegativeSelfTests), result.RuleStatusList.Count);
RulesVerifier verifier = new(new RulesVerifierOptions());
RulesVerifierResult result = verifier.Verify(set);
Assert.IsTrue(result.Verified);
Assert.AreNotEqual(0, result.RuleStatuses.Count);
Assert.AreNotEqual(0, result.CompiledRuleSet.GetAppInspectorRules().Count());
}
foreach (var rule in result.RuleStatusList.Where(x => !x.HasPositiveSelfTests))
logger.Log(LogLevel.Warning, "Rule {0} does not have any positive test cases", rule.RulesId);
foreach (var rule in result.RuleStatusList.Where(x => !x.HasNegativeSelfTests))
logger.Log(LogLevel.Warning, "Rule {0} does not have any negative test cases", rule.RulesId);
Assert.AreEqual(VerifyRulesResult.ExitCode.Verified, result.ResultCode);
Assert.AreNotEqual(0, result.RuleStatusList.Count);
}
}
[TestMethod]
public void VerifyNonZeroDefaultRules()
{
var set = RuleSetUtils.GetDefaultRuleSet();
Assert.IsTrue(set.Any());
RulesVerifier verifier = new(new RulesVerifierOptions());
var result = verifier.Verify(set);
Assert.IsTrue(result.Verified);
Assert.AreNotEqual(0, result.RuleStatuses.Count);
Assert.AreNotEqual(0, result.CompiledRuleSet.GetAppInspectorRules().Count());
}
}

Просмотреть файл

@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.ApplicationInspector.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@ -8,13 +7,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Serilog.Events;
namespace AppInspector.Tests.Languages
namespace AppInspector.Tests.Languages;
[TestClass]
[ExcludeFromCodeCoverage]
public class LanguagesTests
{
[TestClass]
[ExcludeFromCodeCoverage]
public class LanguagesTests
{
readonly string comments_z = @"
private readonly string comments_z = @"
[
{
""language"": [
@ -25,7 +24,8 @@ namespace AppInspector.Tests.Languages
""suffix"": ""*/""
}
]";
readonly string languages_z = @"
private readonly string languages_z = @"
[
{
""name"": ""z"",
@ -34,66 +34,78 @@ namespace AppInspector.Tests.Languages
}
]";
private string testLanguagesPath = string.Empty;
private string testCommentsPath = string.Empty;
private string invalidTestLanguagesPath = string.Empty;
private string invalidTestCommentsPath = string.Empty;
private ILoggerFactory _factory = new NullLoggerFactory();
private string invalidTestCommentsPath = string.Empty;
private string invalidTestLanguagesPath = string.Empty;
private string testCommentsPath = string.Empty;
private ILoggerFactory _factory = new NullLoggerFactory();
[TestInitialize]
public void InitOutput()
{
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
testLanguagesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_languages.json");
testCommentsPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_comments.json");
File.WriteAllText(testLanguagesPath, languages_z);
File.WriteAllText(testCommentsPath, comments_z);
invalidTestLanguagesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_languages_invalid.json");
invalidTestCommentsPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_comments_invalid.json");
File.WriteAllText(invalidTestLanguagesPath, languages_z.Trim().Substring(1)); // Not a valid json array, should be missing the opening [
File.WriteAllText(invalidTestCommentsPath, comments_z.Trim().Substring(1)); // Not a valid json, should be missing the opening [
_factory = new LogOptions() {ConsoleVerbosityLevel = LogEventLevel.Verbose}.GetLoggerFactory();
}
private string testLanguagesPath = string.Empty;
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
[TestMethod]
public void DetectCustomLanguage()
{
var languages = Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, testCommentsPath, testLanguagesPath);
Assert.IsTrue(languages.FromFileNameOut("afilename.z", out var language));
Assert.AreEqual("z", language.Name);
Assert.IsFalse(languages.FromFileNameOut("afilename.c", out var _));
}
[TestMethod]
public void EmptyLanguagesOnInvalidCommentsAndLanguages()
{
Assert.ThrowsException<JsonSerializationException>(() => Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, invalidTestCommentsPath, null));
Assert.ThrowsException<JsonSerializationException>(() => Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, null, invalidTestLanguagesPath));
}
[TestInitialize]
public void InitOutput()
{
Directory.CreateDirectory(TestHelpers.GetPath(TestHelpers.AppPath.testOutput));
testLanguagesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_languages.json");
testCommentsPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_comments.json");
File.WriteAllText(testLanguagesPath, languages_z);
File.WriteAllText(testCommentsPath, comments_z);
invalidTestLanguagesPath = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput),
"test_languages_invalid.json");
invalidTestCommentsPath =
Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "test_comments_invalid.json");
File.WriteAllText(invalidTestLanguagesPath,
languages_z.Trim().Substring(1)); // Not a valid json array, should be missing the opening [
File.WriteAllText(invalidTestCommentsPath,
comments_z.Trim().Substring(1)); // Not a valid json, should be missing the opening [
_factory = new LogOptions { ConsoleVerbosityLevel = LogEventLevel.Verbose }.GetLoggerFactory();
}
[TestMethod]
public void DetectLanguageAsFileNameLanguage()
{
Microsoft.ApplicationInspector.RulesEngine.Languages languages = new(_factory);
Assert.IsTrue(languages.FromFileNameOut("package.json", out var language));
Assert.AreEqual("package.json",language.Name);
}
[DataRow(null, false)] // No way to determine language
[DataRow("", false)] // No way to determine language
[DataRow("validfilename.json", false)] //This test uses the .z test comments and languages from this file.
[DataRow("validfilename.z", true)]
[TestMethod]
public void ReturnFalseWithInvalidFilename(string? filename, bool expected)
{
var languages = Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, testCommentsPath, testLanguagesPath);
Assert.AreEqual(expected,languages.FromFileNameOut(filename!, out _));
}
[ClassCleanup]
public static void CleanUp()
{
Directory.Delete(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), true);
}
[TestMethod]
public void DetectCustomLanguage()
{
var languages =
Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, testCommentsPath,
testLanguagesPath);
Assert.IsTrue(languages.FromFileNameOut("afilename.z", out var language));
Assert.AreEqual("z", language.Name);
Assert.IsFalse(languages.FromFileNameOut("afilename.c", out var _));
}
[TestMethod]
public void EmptyLanguagesOnInvalidCommentsAndLanguages()
{
Assert.ThrowsException<JsonSerializationException>(() =>
Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory,
invalidTestCommentsPath));
Assert.ThrowsException<JsonSerializationException>(() =>
Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, null,
invalidTestLanguagesPath));
}
[TestMethod]
public void DetectLanguageAsFileNameLanguage()
{
Microsoft.ApplicationInspector.RulesEngine.Languages languages = new(_factory);
Assert.IsTrue(languages.FromFileNameOut("package.json", out var language));
Assert.AreEqual("package.json", language.Name);
}
[DataRow(null, false)] // No way to determine language
[DataRow("", false)] // No way to determine language
[DataRow("validfilename.json", false)] //This test uses the .z test comments and languages from this file.
[DataRow("validfilename.z", true)]
[TestMethod]
public void ReturnFalseWithInvalidFilename(string? filename, bool expected)
{
var languages =
Microsoft.ApplicationInspector.RulesEngine.Languages.FromConfigurationFiles(_factory, testCommentsPath,
testLanguagesPath);
Assert.AreEqual(expected, languages.FromFileNameOut(filename!, out _));
}
}

Просмотреть файл

@ -13,52 +13,6 @@ namespace AppInspector.Tests.RuleProcessor;
[TestClass]
public class LoadRulesTests
{
[TestInitialize]
public void TestInit()
{
_logger = _loggerFactory.CreateLogger<LoadRulesTests>();
Directory.CreateDirectory(Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests"));
}
[TestCleanup]
public void TestCleanup()
{
Directory.Delete(Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests"), true);
}
private readonly ILoggerFactory _loggerFactory = new LogOptions(){ ConsoleVerbosityLevel = LogEventLevel.Verbose }.GetLoggerFactory();
private ILogger _logger = new NullLogger<WithinClauseTests>();
[TestMethod]
public void AddFileByPath()
{
RuleSet rules = new(_loggerFactory);
string multiLineRuleLoc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests", "multi_line_rule_file.json");
File.WriteAllText(multiLineRuleLoc, multiLineRule);
rules.AddPath(multiLineRuleLoc, "multiline-tests");
Assert.AreEqual(1, rules.Count());
}
[TestMethod]
public void AddDirectoryByPath()
{
RuleSet rules = new(_loggerFactory);
string multiLineRuleLoc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests", "multi_line_rule_file.json");
File.WriteAllText(multiLineRuleLoc, multiLineRule);
string rule2Loc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests", "rule2_file.json");
File.WriteAllText(rule2Loc, rule2);
rules.AddPath(Path.GetDirectoryName(multiLineRuleLoc) ?? throw new ArgumentNullException(nameof(multiLineRuleLoc)), "multiline-tests");
Assert.AreEqual(2, rules.Count());
}
[TestMethod]
public void AddInvalidPath()
{
RuleSet rules = new(_loggerFactory);
string multiLineRuleLoc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "ThisIsDefinitelyNotADirectoryThatExists");
Assert.ThrowsException<ArgumentException>(() => rules.AddPath(multiLineRuleLoc, "multiline-tests"));
}
private const string multiLineRule = @"[
{
""id"": ""SA000005"",
@ -84,7 +38,7 @@ public class LoadRulesTests
""_comment"": """"
}
]";
// Same rule as above but different id so both can be added
private const string rule2 = @"[
{
@ -111,4 +65,58 @@ public class LoadRulesTests
""_comment"": """"
}
]";
private readonly ILoggerFactory _loggerFactory =
new LogOptions { ConsoleVerbosityLevel = LogEventLevel.Verbose }.GetLoggerFactory();
private ILogger _logger = new NullLogger<WithinClauseTests>();
[TestInitialize]
public void TestInit()
{
_logger = _loggerFactory.CreateLogger<LoadRulesTests>();
Directory.CreateDirectory(Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests"));
}
[TestCleanup]
public void TestCleanup()
{
Directory.Delete(Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests"), true);
}
[TestMethod]
public void AddFileByPath()
{
RuleSet rules = new(_loggerFactory);
var multiLineRuleLoc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests",
"multi_line_rule_file.json");
File.WriteAllText(multiLineRuleLoc, multiLineRule);
rules.AddPath(multiLineRuleLoc, "multiline-tests");
Assert.AreEqual(1, rules.Count());
}
[TestMethod]
public void AddDirectoryByPath()
{
RuleSet rules = new(_loggerFactory);
var multiLineRuleLoc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests",
"multi_line_rule_file.json");
File.WriteAllText(multiLineRuleLoc, multiLineRule);
var rule2Loc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput), "LoadRuleTests",
"rule2_file.json");
File.WriteAllText(rule2Loc, rule2);
rules.AddPath(
Path.GetDirectoryName(multiLineRuleLoc) ?? throw new ArgumentNullException(nameof(multiLineRuleLoc)),
"multiline-tests");
Assert.AreEqual(2, rules.Count());
}
[TestMethod]
public void AddInvalidPath()
{
RuleSet rules = new(_loggerFactory);
var multiLineRuleLoc = Path.Combine(TestHelpers.GetPath(TestHelpers.AppPath.testOutput),
"ThisIsDefinitelyNotADirectoryThatExists");
Assert.ThrowsException<ArgumentException>(() => rules.AddPath(multiLineRuleLoc, "multiline-tests"));
}
}

Просмотреть файл

@ -5,181 +5,16 @@ using System.Linq;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
using Microsoft.CST.OAT;
using Microsoft.CST.RecursiveExtractor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.RuleProcessor
namespace AppInspector.Tests.RuleProcessor;
[TestClass]
[ExcludeFromCodeCoverage]
public class RegexWithIndexTests
{
[TestClass]
[ExcludeFromCodeCoverage]
public class RegexWithIndexTests
{
private readonly Microsoft.ApplicationInspector.RulesEngine.Languages _languages = new();
[TestMethod]
public void NoDictDataAllowed()
{
RuleSet rules = new(null);
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().DictData = new() { new KeyValuePair<string, string>("test","test") };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void NoData()
{
RuleSet rules = new(null);
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new();
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void InvalidRegex()
{
RuleSet rules = new(null);
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new() { "^($" };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void InvalidRegexWhenAnalyzing()
{
RuleSet rules = new(null);
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new() { "^($" };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.Analyze(rules.GetOatRules(),
new TextContainer("TestContent", "csharp", new Microsoft.ApplicationInspector.RulesEngine.Languages()));
Assert.AreEqual(0, issues.Count());
}
[TestMethod]
public void MultiLine()
{
RuleSet rules = new(null);
rules.AddString(multiLineRule, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(multiLineData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void MultiLineCaseInsensitive()
{
RuleSet rules = new(null);
rules.AddString(multiLineCaseInsensitiveRule, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(multiLineData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(2, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void MultiLineRuleWithSingleLineData()
{
RuleSet rules = new(null);
rules.AddString(multiLineRule, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(singleLineData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(0, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void MultiLineRuleWithoutOptionSet()
{
RuleSet rules = new(null);
rules.AddString(multiLineRuleWithoutMultiLine, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(singleLineData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(0, matches.Count);
}
else
{
Assert.Fail();
}
}
[DataRow(jsonRule)]
[DataRow(jsonAndXmlRule)]
[DataTestMethod]
public void JsonRule(string rule)
{
RuleSet rules = new(null);
rules.AddString(rule, "JsonTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions(){AllowAllTagsInBuildFiles = true});
if (_languages.FromFileNameOut("test.json", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(jsonData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.json", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[DataRow(xmlRule)]
[DataRow(jsonAndXmlRule)]
[DataTestMethod]
public void XmlRule(string rule)
{
RuleSet rules = new(null);
rules.AddString(rule, "XmlTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions(){AllowAllTagsInBuildFiles = true});
if (_languages.FromFileNameOut("test.xml", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(xmlData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.xml", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
private const string jsonAndXmlRule = @"[
private const string jsonAndXmlRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.JSONandXML"",
@ -203,8 +38,8 @@ namespace AppInspector.Tests.RuleProcessor
""_comment"": """"
}
]";
private const string jsonRule = @"[
private const string jsonRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.JSON"",
@ -227,8 +62,8 @@ namespace AppInspector.Tests.RuleProcessor
""_comment"": """"
}
]";
private const string xmlRule = @"[
private const string xmlRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.XML"",
@ -252,8 +87,8 @@ namespace AppInspector.Tests.RuleProcessor
}
]";
private const string jsonData =
@"{
private const string jsonData =
@"{
""books"":
[
{
@ -289,9 +124,9 @@ namespace AppInspector.Tests.RuleProcessor
]
}
";
private const string xmlData =
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
private const string xmlData =
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<bookstore>
<book genre=""autobiography"" publicationdate=""1981-03-22"" ISBN=""1-861003-11-0"">
<title>The Autobiography of Benjamin Franklin</title>
@ -319,7 +154,7 @@ namespace AppInspector.Tests.RuleProcessor
</bookstore>
";
private const string multiLineRuleWithoutMultiLine = @"[
private const string multiLineRuleWithoutMultiLine = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.MultiLine"",
@ -342,7 +177,7 @@ namespace AppInspector.Tests.RuleProcessor
}
]";
private const string multiLineRule = @"[
private const string multiLineRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.MultiLine"",
@ -368,7 +203,7 @@ namespace AppInspector.Tests.RuleProcessor
}
]";
private const string multiLineCaseInsensitiveRule = @"[
private const string multiLineCaseInsensitiveRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.MultiLine"",
@ -395,14 +230,182 @@ namespace AppInspector.Tests.RuleProcessor
}
]";
const string multiLineData = @"
private const string multiLineData = @"
race
CAR
race
car";
const string singleLineData = @"
private const string singleLineData = @"
raceCAR
racecar";
private readonly Microsoft.ApplicationInspector.RulesEngine.Languages _languages = new();
[TestMethod]
public void NoDictDataAllowed()
{
RuleSet rules = new();
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().DictData = new List<KeyValuePair<string, string>>
{ new KeyValuePair<string, string>("test", "test") };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void NoData()
{
RuleSet rules = new();
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new List<string>();
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void InvalidRegex()
{
RuleSet rules = new();
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new List<string> { "^($" };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void InvalidRegexWhenAnalyzing()
{
RuleSet rules = new();
rules.AddString(multiLineRule, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new List<string> { "^($" };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.Analyze(rules.GetOatRules(),
new TextContainer("TestContent", "csharp", new Microsoft.ApplicationInspector.RulesEngine.Languages()));
Assert.AreEqual(0, issues.Count());
}
[TestMethod]
public void MultiLine()
{
RuleSet rules = new();
rules.AddString(multiLineRule, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(multiLineData, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void MultiLineCaseInsensitive()
{
RuleSet rules = new();
rules.AddString(multiLineCaseInsensitiveRule, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(multiLineData, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(2, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void MultiLineRuleWithSingleLineData()
{
RuleSet rules = new();
rules.AddString(multiLineRule, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(singleLineData, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(0, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void MultiLineRuleWithoutOptionSet()
{
RuleSet rules = new();
rules.AddString(multiLineRuleWithoutMultiLine, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(singleLineData, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(0, matches.Count);
}
else
{
Assert.Fail();
}
}
[DataRow(jsonRule)]
[DataRow(jsonAndXmlRule)]
[DataTestMethod]
public void JsonRule(string rule)
{
RuleSet rules = new();
rules.AddString(rule, "JsonTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules,
new RuleProcessorOptions { AllowAllTagsInBuildFiles = true });
if (_languages.FromFileNameOut("test.json", out var info))
{
var matches = processor.AnalyzeFile(jsonData, new FileEntry("test.json", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[DataRow(xmlRule)]
[DataRow(jsonAndXmlRule)]
[DataTestMethod]
public void XmlRule(string rule)
{
RuleSet rules = new();
rules.AddString(rule, "XmlTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules,
new RuleProcessorOptions { AllowAllTagsInBuildFiles = true });
if (_languages.FromFileNameOut("test.xml", out var info))
{
var matches = processor.AnalyzeFile(xmlData, new FileEntry("test.xml", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
}

Просмотреть файл

@ -7,42 +7,6 @@ namespace AppInspector.Tests.RuleProcessor;
[TestClass]
public class RuleTests
{
[TestMethod]
public void ModifySource()
{
RuleSet rules = new(null);
var originalSource = "TestRules";
rules.AddString(MultiLineRuleWithoutMultiLine, originalSource);
var rule = rules.First();
Assert.AreEqual(originalSource, rule.Source);
var newSource = "Somewhere";
rule.Source = newSource;
Assert.AreEqual(newSource, rule.Source);
}
[TestMethod]
public void ModifyRuntimeTag()
{
RuleSet rules = new(null);
var originalSource = "TestRules";
rules.AddString(MultiLineRuleWithoutMultiLine, originalSource);
var rule = rules.First();
var newTag = "SomeTag";
rule.RuntimeTag = newTag;
Assert.AreEqual(newTag, rule.RuntimeTag);
}
[TestMethod]
public void ModifyDisabled()
{
RuleSet rules = new(null);
var originalSource = "TestRules";
rules.AddString(MultiLineRuleWithoutMultiLine, originalSource);
var rule = rules.First();
rule.Disabled = true;
Assert.AreEqual(true, rule.Disabled);
}
private const string MultiLineRuleWithoutMultiLine = @"[
{
""id"": ""SA000005"",
@ -65,4 +29,40 @@ public class RuleTests
""_comment"": """"
}
]";
[TestMethod]
public void ModifySource()
{
RuleSet rules = new();
var originalSource = "TestRules";
rules.AddString(MultiLineRuleWithoutMultiLine, originalSource);
var rule = rules.First();
Assert.AreEqual(originalSource, rule.Source);
var newSource = "Somewhere";
rule.Source = newSource;
Assert.AreEqual(newSource, rule.Source);
}
[TestMethod]
public void ModifyRuntimeTag()
{
RuleSet rules = new();
var originalSource = "TestRules";
rules.AddString(MultiLineRuleWithoutMultiLine, originalSource);
var rule = rules.First();
var newTag = "SomeTag";
rule.RuntimeTag = newTag;
Assert.AreEqual(newTag, rule.RuntimeTag);
}
[TestMethod]
public void ModifyDisabled()
{
RuleSet rules = new();
var originalSource = "TestRules";
rules.AddString(MultiLineRuleWithoutMultiLine, originalSource);
var rule = rules.First();
rule.Disabled = true;
Assert.AreEqual(true, rule.Disabled);
}
}

Просмотреть файл

@ -5,291 +5,16 @@ using System.Linq;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
using Microsoft.CST.OAT;
using Microsoft.CST.RecursiveExtractor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.RuleProcessor
namespace AppInspector.Tests.RuleProcessor;
[TestClass]
[ExcludeFromCodeCoverage]
public class SubstringWithIndexTests
{
[TestClass]
[ExcludeFromCodeCoverage]
public class SubstringWithIndexTests
{
private readonly Microsoft.ApplicationInspector.RulesEngine.Languages _languages = new();
[DataRow(jsonStringRule)]
[DataRow(jsonAndXmlStringRule)]
[DataTestMethod]
public void JsonSubstringRule(string rule)
{
RuleSet rules = new(null);
rules.AddString(rule, "JsonTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions(){AllowAllTagsInBuildFiles = true});
if (_languages.FromFileNameOut("test.json", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(jsonData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.json", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[DataRow(xmlStringRule)]
[DataRow(jsonAndXmlStringRule)]
[DataTestMethod]
public void XmlSubstringRule(string rule)
{
RuleSet rules = new(null);
rules.AddString(rule, "XmlTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions(){AllowAllTagsInBuildFiles = true});
if (_languages.FromFileNameOut("test.xml", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(xmlData, new Microsoft.CST.RecursiveExtractor.FileEntry("test.xml", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
private const string jsonAndXmlStringRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.JSONandXML"",
""tags"": [
""Testing.Rules.JSON.JSONandXML""
],
""severity"": ""Critical"",
""description"": ""This rule finds books from the JSON or XML titled with Franklin."",
""patterns"": [
{
""pattern"": ""Franklin"",
""type"": ""string"",
""confidence"": ""High"",
""scopes"": [
""code""
],
""jsonpaths"" : [""$.books[*].title""],
""xpaths"" : [""/bookstore/book/title""]
}
],
""_comment"": """"
}
]";
private const string jsonStringRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.JSON"",
""tags"": [
""Testing.Rules.JSON""
],
""severity"": ""Critical"",
""description"": ""This rule finds books from the JSON titled with Franklin."",
""patterns"": [
{
""pattern"": ""Franklin"",
""type"": ""string"",
""confidence"": ""High"",
""scopes"": [
""code""
],
""jsonpaths"" : [""$.books[*].title""]
}
],
""_comment"": """"
}
]";
private const string xmlStringRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.XML"",
""tags"": [
""Testing.Rules.XML""
],
""severity"": ""Critical"",
""description"": ""This rule finds books from the XML titled with Franklin."",
""patterns"": [
{
""pattern"": ""Franklin"",
""type"": ""string"",
""confidence"": ""High"",
""scopes"": [
""code""
],
""xpaths"" : [""/bookstore/book/title""]
}
],
""_comment"": """"
}
]";
private const string jsonData =
@"{
""books"":
[
{
""category"": ""fiction"",
""title"" : ""A Wild Sheep Chase"",
""author"" : ""Haruki Murakami"",
""price"" : 22.72
},
{
""category"": ""fiction"",
""title"" : ""The Night Watch"",
""author"" : ""Sergei Lukyanenko"",
""price"" : 23.58
},
{
""category"": ""fiction"",
""title"" : ""The Comedians"",
""author"" : ""Graham Greene"",
""price"" : 21.99
},
{
""category"": ""memoir"",
""title"" : ""The Night Watch"",
""author"" : ""David Atlee Phillips"",
""price"" : 260.90
},
{
""category"": ""memoir"",
""title"" : ""The Autobiography of Benjamin Franklin"",
""author"" : ""Benjamin Franklin"",
""price"" : 123.45
}
]
}
";
private const string xmlData =
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<bookstore>
<book genre=""autobiography"" publicationdate=""1981-03-22"" ISBN=""1-861003-11-0"">
<title>The Autobiography of Benjamin Franklin</title>
<author>
<first-name>Benjamin</first-name>
<last-name>Franklin</last-name>
</author>
<price>8.99</price>
</book>
<book genre=""novel"" publicationdate=""1967-11-17"" ISBN=""0-201-63361-2"">
<title>The Confidence Man</title>
<author>
<first-name>Herman</first-name>
<last-name>Melville</last-name>
</author>
<price>11.99</price>
</book>
<book genre=""philosophy"" publicationdate=""1991-02-15"" ISBN=""1-861001-57-6"">
<title>The Gorgias</title>
<author>
<name>Plato</name>
</author>
<price>9.99</price>
</book>
</bookstore>
";
[TestMethod]
public void NoDictDataAllowed()
{
RuleSet rules = new(null);
rules.AddString(wordBoundaryEnabledCaseSensitive, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().DictData = new() { new KeyValuePair<string, string>("test", "test") };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void NoData()
{
RuleSet rules = new(null);
rules.AddString(wordBoundaryEnabledCaseSensitive, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new();
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void WordBoundaryEnabledCaseSensitive()
{
RuleSet rules = new(null);
rules.AddString(wordBoundaryEnabledCaseSensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(data, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void WordBoundaryDisabledCaseSensitive()
{
RuleSet rules = new(null);
rules.AddString(wordBoundaryDisabledCaseSensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(data, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(2, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void WordBoundaryEnabledCaseInsensitive()
{
RuleSet rules = new(null);
rules.AddString(wordBoundaryEnabledCaseInsensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(data, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(2, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void WordBoundaryDisabledCaseInsensitive()
{
RuleSet rules = new(null);
rules.AddString(wordBoundaryDisabledCaseInsensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out LanguageInfo info))
{
List<MatchRecord> matches = processor.AnalyzeFile(data, new Microsoft.CST.RecursiveExtractor.FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(4, matches.Count);
}
else
{
Assert.Fail();
}
}
private const string wordBoundaryDisabledCaseSensitive = @"[
private const string wordBoundaryDisabledCaseSensitive = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.WordBoundary"",
@ -311,7 +36,8 @@ namespace AppInspector.Tests.RuleProcessor
""_comment"": """"
}
]";
private const string wordBoundaryDisabledCaseInsensitive = @"[
private const string wordBoundaryDisabledCaseInsensitive = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.WordBoundary"",
@ -336,7 +62,8 @@ namespace AppInspector.Tests.RuleProcessor
""_comment"": """"
}
]";
private const string wordBoundaryEnabledCaseSensitive = @"[
private const string wordBoundaryEnabledCaseSensitive = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.WordBoundary"",
@ -358,7 +85,8 @@ namespace AppInspector.Tests.RuleProcessor
""_comment"": """"
}
]";
private const string wordBoundaryEnabledCaseInsensitive = @"[
private const string wordBoundaryEnabledCaseInsensitive = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.WordBoundary"",
@ -384,10 +112,108 @@ namespace AppInspector.Tests.RuleProcessor
}
]";
const string data = @"
private const string data = @"
raceCARwithmorestuff
racecarwithmorestuff
raceCAR withmorestuff
racecar withmorestuff";
private readonly Microsoft.ApplicationInspector.RulesEngine.Languages _languages = new();
[TestMethod]
public void NoDictDataAllowed()
{
RuleSet rules = new();
rules.AddString(wordBoundaryEnabledCaseSensitive, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().DictData = new List<KeyValuePair<string, string>>
{ new KeyValuePair<string, string>("test", "test") };
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void NoData()
{
RuleSet rules = new();
rules.AddString(wordBoundaryEnabledCaseSensitive, "TestRules");
var theRule = rules.GetOatRules().First();
theRule.Clauses.First().Data = new List<string>();
Analyzer analyzer = new ApplicationInspectorAnalyzer();
var issues = analyzer.EnumerateRuleIssues(theRule);
Assert.AreEqual(1, issues.Count());
}
[TestMethod]
public void WordBoundaryEnabledCaseSensitive()
{
RuleSet rules = new();
rules.AddString(wordBoundaryEnabledCaseSensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(data, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void WordBoundaryDisabledCaseSensitive()
{
RuleSet rules = new();
rules.AddString(wordBoundaryDisabledCaseSensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(data, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(2, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void WordBoundaryEnabledCaseInsensitive()
{
RuleSet rules = new();
rules.AddString(wordBoundaryEnabledCaseInsensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(data, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(2, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void WordBoundaryDisabledCaseInsensitive()
{
RuleSet rules = new();
rules.AddString(wordBoundaryDisabledCaseInsensitive, "TestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules, new RuleProcessorOptions());
if (_languages.FromFileNameOut("test.c", out var info))
{
var matches = processor.AnalyzeFile(data, new FileEntry("test.cs", new MemoryStream()), info);
Assert.AreEqual(4, matches.Count);
}
else
{
Assert.Fail();
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,270 @@
using System.IO;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.CST.RecursiveExtractor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AppInspector.Tests.RuleProcessor;
[TestClass]
public class XmlAndJsonTests
{
private readonly Microsoft.ApplicationInspector.RulesEngine.Languages _languages = new();
private const string jsonAndXmlStringRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.JSONandXML"",
""tags"": [
""Testing.Rules.JSON.JSONandXML""
],
""severity"": ""Critical"",
""description"": ""This rule finds books from the JSON or XML titled with Franklin."",
""patterns"": [
{
""pattern"": ""Franklin"",
""type"": ""string"",
""confidence"": ""High"",
""scopes"": [
""code""
],
""jsonpaths"" : [""$.books[*].title""],
""xpaths"" : [""/bookstore/book/title""]
}
],
""_comment"": """"
}
]";
private const string jsonStringRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.JSON"",
""tags"": [
""Testing.Rules.JSON""
],
""severity"": ""Critical"",
""description"": ""This rule finds books from the JSON titled with Franklin."",
""patterns"": [
{
""pattern"": ""Franklin"",
""type"": ""string"",
""confidence"": ""High"",
""scopes"": [
""code""
],
""jsonpaths"" : [""$.books[*].title""]
}
],
""_comment"": """"
}
]";
private const string xmlStringRule = @"[
{
""id"": ""SA000005"",
""name"": ""Testing.Rules.XML"",
""tags"": [
""Testing.Rules.XML""
],
""severity"": ""Critical"",
""description"": ""This rule finds books from the XML titled with Franklin."",
""patterns"": [
{
""pattern"": ""Franklin"",
""type"": ""string"",
""confidence"": ""High"",
""scopes"": [
""code""
],
""xpaths"" : [""/bookstore/book/title""]
}
],
""_comment"": """"
}
]";
private const string jsonData =
@"{
""books"":
[
{
""category"": ""fiction"",
""title"" : ""A Wild Sheep Chase"",
""author"" : ""Haruki Murakami"",
""price"" : 22.72
},
{
""category"": ""fiction"",
""title"" : ""The Night Watch"",
""author"" : ""Sergei Lukyanenko"",
""price"" : 23.58
},
{
""category"": ""fiction"",
""title"" : ""The Comedians"",
""author"" : ""Graham Greene"",
""price"" : 21.99
},
{
""category"": ""memoir"",
""title"" : ""The Night Watch"",
""author"" : ""David Atlee Phillips"",
""price"" : 260.90
},
{
""category"": ""memoir"",
""title"" : ""The Autobiography of Benjamin Franklin"",
""author"" : ""Benjamin Franklin"",
""price"" : 123.45
}
]
}
";
private const string xmlData =
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<bookstore>
<book genre=""autobiography"" publicationdate=""1981-03-22"" ISBN=""1-861003-11-0"">
<title>The Autobiography of Benjamin Franklin</title>
<author>
<first-name>Benjamin</first-name>
<last-name>Franklin</last-name>
</author>
<price>8.99</price>
</book>
<book genre=""novel"" publicationdate=""1967-11-17"" ISBN=""0-201-63361-2"">
<title>The Confidence Man</title>
<author>
<first-name>Herman</first-name>
<last-name>Melville</last-name>
</author>
<price>11.99</price>
</book>
<book genre=""philosophy"" publicationdate=""1991-02-15"" ISBN=""1-861001-57-6"">
<title>The Gorgias</title>
<author>
<name>Plato</name>
</author>
<price>9.99</price>
</book>
</bookstore>
";
[DataRow(jsonStringRule)]
[DataRow(jsonAndXmlStringRule)]
[DataTestMethod]
public void JsonStringRule(string rule)
{
RuleSet rules = new();
rules.AddString(rule, "JsonTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules,
new RuleProcessorOptions { AllowAllTagsInBuildFiles = true });
if (_languages.FromFileNameOut("test.json", out var info))
{
var matches = processor.AnalyzeFile(jsonData, new FileEntry("test.json", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[DataRow(xmlStringRule)]
[DataRow(jsonAndXmlStringRule)]
[DataTestMethod]
public void XmlStringRule(string rule)
{
RuleSet rules = new();
rules.AddString(rule, "XmlTestRules");
Microsoft.ApplicationInspector.RulesEngine.RuleProcessor processor = new(rules,
new RuleProcessorOptions { AllowAllTagsInBuildFiles = true });
if (_languages.FromFileNameOut("test.xml", out var info))
{
var matches = processor.AnalyzeFile(xmlData, new FileEntry("test.xml", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
else
{
Assert.Fail();
}
}
[TestMethod]
public void TestXmlWithAndWithoutNamespace()
{
var content = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<project xmlns=""http://maven.apache.org/POM/4.0.0"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:schemaLocation=""http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"">
<modelVersion>4.0.0</modelVersion>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description />
<properties>
<java.version>17</java.version>
</properties>
</project>";
// The same as above but with no namespace specified
var noNamespaceContent = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description />
<properties>
<java.version>17</java.version>
</properties>
</project>";
var rule = @"[{
""name"": ""Source code: Java 17"",
""id"": ""CODEJAVA000000"",
""description"": ""Java 17 maven configuration"",
""applies_to_file_regex"": [
""pom.xml""
],
""tags"": [
""Code.Java.17""
],
""severity"": ""critical"",
""patterns"": [
{
""pattern"": ""17"",
""xpaths"" : [""/*[local-name(.)='project']/*[local-name(.)='properties']/*[local-name(.)='java.version']""],
""type"": ""regex"",
""scopes"": [
""code""
],
""modifiers"": [
""i""
],
""confidence"": ""high""
}
]
}]";
RuleSet rules = new();
var originalSource = "TestRules";
rules.AddString(rule, originalSource);
var analyzer = new Microsoft.ApplicationInspector.RulesEngine.RuleProcessor(rules,
new RuleProcessorOptions { Parallel = false, AllowAllTagsInBuildFiles = true });
if (_languages.FromFileNameOut("pom.xml", out var info))
{
var matches = analyzer.AnalyzeFile(content, new FileEntry("pom.xml", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
matches = analyzer.AnalyzeFile(noNamespaceContent, new FileEntry("pom.xml", new MemoryStream()), info);
Assert.AreEqual(1, matches.Count);
}
}
}

Просмотреть файл

@ -5,135 +5,140 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.ApplicationInspector.Logging;
using Microsoft.Extensions.Logging;
using Serilog.Events;
namespace AppInspector.Tests
namespace AppInspector.Tests;
[ExcludeFromCodeCoverage]
public static class TestHelpers
{
[ExcludeFromCodeCoverage]
public static class TestHelpers
public enum AppPath
{
public static ILoggerFactory GenerateLoggerFactory(string logName = "testLog.txt", Serilog.Events.LogEventLevel fileLevel = Serilog.Events.LogEventLevel.Verbose, Serilog.Events.LogEventLevel consoleLevel = Serilog.Events.LogEventLevel.Debug) =>
new LogOptions()
{
LogFileLevel = fileLevel,
LogFilePath = Path.Combine(GetPath(AppPath.testLogOutput), logName),
ConsoleVerbosityLevel = consoleLevel
}.GetLoggerFactory();
public enum AppPath { basePath, testSource, testRules, testOutput, defaultRules, appInspectorCLI, testLogOutput };
basePath,
testSource,
testRules,
testOutput,
defaultRules,
appInspectorCLI,
testLogOutput
}
private static string _basePath = string.Empty;
private static string _basePath = string.Empty;
private static string GetBaseAppPath()
public static ILoggerFactory GenerateLoggerFactory(string logName = "testLog.txt",
LogEventLevel fileLevel = LogEventLevel.Verbose, LogEventLevel consoleLevel = LogEventLevel.Debug)
{
return new LogOptions
{
if (!string.IsNullOrEmpty(_basePath))
{
return _basePath;
}
LogFileLevel = fileLevel,
LogFilePath = Path.Combine(GetPath(AppPath.testLogOutput), logName),
ConsoleVerbosityLevel = consoleLevel
}.GetLoggerFactory();
}
_basePath = Path.GetFullPath(AppContext.BaseDirectory);
return _basePath;
}
private static string GetBaseAppPath()
{
if (!string.IsNullOrEmpty(_basePath)) return _basePath;
public static string GetPath(AppPath pathType)
_basePath = Path.GetFullPath(AppContext.BaseDirectory);
return _basePath;
}
public static string GetPath(AppPath pathType)
{
var result = "";
switch (pathType)
{
string result = "";
switch (pathType)
{
case AppPath.basePath:
result = GetBaseAppPath();
break;
case AppPath.basePath:
result = GetBaseAppPath();
break;
case AppPath.testSource:
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "source");
break;
case AppPath.testSource:
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "source");
break;
case AppPath.testRules://Packrules default output use
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "customrules");
break;
case AppPath.testRules: //Packrules default output use
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "customrules");
break;
case AppPath.testOutput://Packrules default output use
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "output");
break;
case AppPath.testOutput: //Packrules default output use
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "output");
break;
case AppPath.defaultRules:
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector", "rules");
break;
case AppPath.defaultRules:
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector", "rules");
break;
case AppPath.appInspectorCLI:
case AppPath.appInspectorCLI:
#if DEBUG
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.CLI", "bin", "debug", "net6.0", "applicationinspector.cli.exe");
result =
Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.CLI", "bin", "debug", "net6.0", "applicationinspector.cli.exe");
#else
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.CLI", "bin", "release", "net6.0", "applicationinspector.cli.exe");
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.CLI", "bin", "release",
"net6.0", "applicationinspector.cli.exe");
#endif
break;
case AppPath.testLogOutput:
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "logs");
break;
}
result = Path.GetFullPath(result);
return result;
break;
case AppPath.testLogOutput:
result = Path.Combine(GetBaseAppPath(), "..", "..", "..", "..", "AppInspector.Tests", "logs");
break;
}
public static List<string> GetTagsFromFile(string[] contentLines)
result = Path.GetFullPath(result);
return result;
}
public static List<string> GetTagsFromFile(string[] contentLines)
{
List<string> results = new();
int i;
for (i = 0; i < contentLines.Length; i++)
if (contentLines[i].Contains("[UniqueTags]"))
break;
i++; //get past marker
while (!contentLines[i].Contains("Select Counters"))
{
List<string> results = new();
int i;
for (i = 0; i < contentLines.Length; i++)
{
if (contentLines[i].Contains("[UniqueTags]"))
{
break;
}
}
i++;//get past marker
while (!contentLines[i].Contains("Select Counters"))
{
results.Add(contentLines[i++]);
if (i > contentLines.Length)
{
break;
}
}
return results;
results.Add(contentLines[i++]);
if (i > contentLines.Length) break;
}
public static int RunProcess(string appFilePath, string arguments)
return results;
}
public static int RunProcess(string appFilePath, string arguments)
{
var result = 2;
using (Process process = new())
{
int result = 2;
using (Process process = new())
{
process.StartInfo.FileName = appFilePath;
process.StartInfo.Arguments = arguments;
process.Start();
process.WaitForExit();
result = process.ExitCode;
}
return result;
process.StartInfo.FileName = appFilePath;
process.StartInfo.Arguments = arguments;
process.Start();
process.WaitForExit();
result = process.ExitCode;
}
public static int RunProcess(string appFilePath, string arguments, out string consoleContent)
return result;
}
public static int RunProcess(string appFilePath, string arguments, out string consoleContent)
{
int result;
using (Process process = new())
{
int result;
using (Process process = new())
{
process.StartInfo.FileName = appFilePath;
process.StartInfo.Arguments = arguments;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
consoleContent = process.StandardOutput.ReadToEnd();
process.WaitForExit();
result = process.ExitCode;
process.StartInfo.FileName = appFilePath;
process.StartInfo.Arguments = arguments;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
consoleContent = process.StandardOutput.ReadToEnd();
process.WaitForExit();
result = process.ExitCode;
using StreamWriter standardOutput = new(Console.OpenStandardOutput());
standardOutput.AutoFlush = true;
Console.SetOut(standardOutput);
}
return result;
using StreamWriter standardOutput = new(Console.OpenStandardOutput());
standardOutput.AutoFlush = true;
Console.SetOut(standardOutput);
}
return result;
}
}

Просмотреть файл

@ -1,94 +1,94 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.Commands</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.Commands</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.Commands</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
<PackageId>Microsoft.CST.ApplicationInspector.Commands</PackageId>
<PackageVersion>0.0.0-placeholder</PackageVersion>
<PackageProjectUrl>https://github.com/microsoft/ApplicationInspector</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/ApplicationInspector</RepositoryUrl>
<PackageTags>Security Static Analyzer</PackageTags>
<Description>Microsoft Application Inspector is a software source code analysis tool that helps identify and surface well-known features and other interesting characteristics of source code to aid in determining what the software is or what it does.</Description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<HighEntropyVA>true</HighEntropyVA>
<Product>Application Inspector</Product>
<Authors>Microsoft</Authors>
<Version>0.0.0-placeholder</Version>
<AssemblyName>ApplicationInspector.Commands</AssemblyName>
<RootNamespace>Microsoft.ApplicationInspector.Commands</RootNamespace>
<Company>Microsoft</Company>
<SignAssembly>true</SignAssembly>
<AssemblyVersion>0.0.0</AssemblyVersion>
<FileVersion>0.0.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIcon>icon-128.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))"/>
</ItemGroup>
</Target>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>1701;1702;2225</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants/>
</PropertyGroup>
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
<EmbeddedResource Include="rules\default\**\*.json"/>
</ItemGroup>
</Target>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>1701;1702;2225</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
<PackageReference Include="DotLiquid" Version="2.2.656"/>
<PackageReference Include="Glob" Version="1.1.9"/>
<PackageReference Include="Microsoft.CST.OAT" Version="1.2.25"/>
<PackageReference Include="Microsoft.CST.RecursiveExtractor" Version="1.1.11"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
<PackageReference Include="ShellProgressBar" Version="5.2.0"/>
<PackageReference Include="System.Reflection.Metadata" Version="6.0.1"/>
<ItemGroup>
<EmbeddedResource Include="rules\default\**\*.json" />
</ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="DotLiquid" Version="2.2.656" />
<PackageReference Include="Glob" Version="1.1.9" />
<PackageReference Include="Microsoft.CST.OAT" Version="1.2.25" />
<PackageReference Include="Microsoft.CST.RecursiveExtractor" Version="1.1.11" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
<PackageReference Include="System.Reflection.Metadata" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Common\AppInspector.Common.csproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>ApplicationInspector.Common.dll</IncludeAssets>
</ProjectReference>
<ProjectReference Include="..\AppInspector.RulesEngine\AppInspector.RulesEngine.csproj"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInspector.Common\AppInspector.Common.csproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>ApplicationInspector.Common.dll</IncludeAssets>
</ProjectReference>
<ProjectReference Include="..\AppInspector.RulesEngine\AppInspector.RulesEngine.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="..\LICENSE.txt" Pack="true" PackagePath="" />
<None Include="..\icon-128.png" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig"/>
<None Include="..\LICENSE.txt" Pack="true" PackagePath=""/>
<None Include="..\icon-128.png" Pack="true" PackagePath=""/>
</ItemGroup>
</Project>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,132 +1,115 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Commands
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.Commands;
/// <summary>
/// Options for the Export Tags command.
/// </summary>
public class ExportTagsOptions
{
using Microsoft.ApplicationInspector.RulesEngine;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
public string? CustomRulesPath { get; set; }
public bool IgnoreDefaultRules { get; set; }
}
/// <summary>
/// Options for the Export Tags command.
/// </summary>
public class ExportTagsOptions
/// <summary>
/// Final result of GetResult call
/// </summary>
public class ExportTagsResult : Result
{
public enum ExitCode
{
public string? CustomRulesPath { get; set; }
public bool IgnoreDefaultRules { get; set; }
Success = 0,
Error = 1,
CriticalError = Utils.ExitCode.CriticalError //ensure common value for final exit log mention
}
/// <summary>
/// Final result of GetResult call
/// </summary>
public class ExportTagsResult : Result
public ExportTagsResult()
{
public enum ExitCode
{
Success = 0,
Error = 1,
CriticalError = Common.Utils.ExitCode.CriticalError //ensure common value for final exit log mention
}
[JsonProperty(Order = 2, PropertyName = "resultCode")]
public ExitCode ResultCode { get; set; }
/// <summary>
/// List of tags exported from specified ruleset
/// </summary>
[JsonProperty(Order = 3, PropertyName = "tagsList")]
public List<string> TagsList { get; set; }
public ExportTagsResult()
{
TagsList = new List<string>();
}
TagsList = new List<string>();
}
[JsonProperty(Order = 2, PropertyName = "resultCode")]
public ExitCode ResultCode { get; set; }
/// <summary>
/// Export command operation manages setp and delivery of ExportResult objects
/// List of tags exported from specified ruleset
/// </summary>
public class ExportTagsCommand
[JsonProperty(Order = 3, PropertyName = "tagsList")]
public List<string> TagsList { get; set; }
}
/// <summary>
/// Export command operation manages setp and delivery of ExportResult objects
/// </summary>
public class ExportTagsCommand
{
private readonly ILogger _logger;
private readonly ILoggerFactory? _loggerFactory;
private readonly ExportTagsOptions _options;
private RuleSet _rules;
public ExportTagsCommand(ExportTagsOptions opt, ILoggerFactory? loggerFactory = null)
{
private readonly ExportTagsOptions _options;
private readonly ILogger _logger;
private readonly ILoggerFactory? _loggerFactory;
private RuleSet _rules;
_options = opt;
_logger = loggerFactory?.CreateLogger<ExportTagsCommand>() ?? NullLogger<ExportTagsCommand>.Instance;
_loggerFactory = loggerFactory;
_rules = new RuleSet();
ConfigRules();
}
public ExportTagsCommand(ExportTagsOptions opt, ILoggerFactory? loggerFactory = null)
private void ConfigRules()
{
_logger.LogTrace("ExportTagsCommand::ConfigRules");
_rules = new RuleSet(_loggerFactory);
if (!_options.IgnoreDefaultRules) _rules = RuleSetUtils.GetDefaultRuleSet(_loggerFactory);
if (!string.IsNullOrEmpty(_options?.CustomRulesPath))
{
_options = opt;
_logger = loggerFactory?.CreateLogger<ExportTagsCommand>() ?? NullLogger<ExportTagsCommand>.Instance;
_loggerFactory = loggerFactory;
_rules = new RuleSet();
ConfigRules();
if (Directory.Exists(_options.CustomRulesPath))
_rules.AddDirectory(_options.CustomRulesPath);
else if (File.Exists(_options.CustomRulesPath))
_rules.AddFile(_options.CustomRulesPath);
else
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_RULE_PATH, _options.CustomRulesPath));
}
private void ConfigRules()
//error check based on ruleset not path enumeration
if (_rules == null || !_rules.Any()) throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_NORULES_SPECIFIED));
}
public ExportTagsResult GetResult()
{
_logger.LogTrace("ExportTagsCommand::Run");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Export Tags");
ExportTagsResult exportTagsResult = new()
{
_logger.LogTrace("ExportTagsCommand::ConfigRules");
_rules = new RuleSet(_loggerFactory);
if (!_options.IgnoreDefaultRules)
{
_rules = RuleSetUtils.GetDefaultRuleSet(_loggerFactory);
}
AppVersion = Utils.GetVersionString()
};
if (!string.IsNullOrEmpty(_options?.CustomRulesPath))
{
if (Directory.Exists(_options.CustomRulesPath))
{
_rules.AddDirectory(_options.CustomRulesPath);
}
else if (File.Exists(_options.CustomRulesPath))
{
_rules.AddFile(_options.CustomRulesPath);
}
else
{
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_RULE_PATH, _options.CustomRulesPath));
}
}
HashSet<string> tags = new();
foreach (var rule in _rules.GetAppInspectorRules())
foreach (var tag in rule.Tags ?? Array.Empty<string>())
tags.Add(tag);
//error check based on ruleset not path enumeration
if (_rules == null || !_rules.Any())
{
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_NORULES_SPECIFIED));
}
}
exportTagsResult.TagsList = tags.ToList();
exportTagsResult.TagsList.Sort();
public ExportTagsResult GetResult()
{
_logger.LogTrace("ExportTagsCommand::Run");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Export Tags");
exportTagsResult.ResultCode = ExportTagsResult.ExitCode.Success;
ExportTagsResult exportTagsResult = new()
{
AppVersion = Common.Utils.GetVersionString()
};
HashSet<string> tags = new();
foreach (var rule in _rules.GetAppInspectorRules())
{
foreach (var tag in rule.Tags ?? Array.Empty<string>())
{
tags.Add(tag);
}
}
exportTagsResult.TagsList = tags.ToList();
exportTagsResult.TagsList.Sort();
exportTagsResult.ResultCode = ExportTagsResult.ExitCode.Success;
return exportTagsResult;
}
return exportTagsResult;
}
}

Просмотреть файл

@ -1,117 +1,112 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Commands
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.Commands;
public class PackRulesOptions
{
using Microsoft.ApplicationInspector.RulesEngine;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationInspector.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
public string? CustomRulesPath { get; set; }
public bool PackEmbeddedRules { get; set; }
public string? CustomCommentsPath { get; set; }
public string? CustomLanguagesPath { get; set; }
public bool DisableRequireUniqueIds { get; set; }
public bool RequireMustMatch { get; set; }
public bool RequireMustNotMatch { get; set; }
}
public class PackRulesOptions
public class PackRulesResult : Result
{
public enum ExitCode
{
public string? CustomRulesPath { get; set; }
public bool NotIndented { get; set; }
public bool PackEmbeddedRules { get; set; }
public string? CustomCommentsPath { get; set; }
public string? CustomLanguagesPath { get; set; }
Success = 0,
Error = 1,
CriticalError = Utils.ExitCode.CriticalError //ensure common value for final exit log mention
}
public class PackRulesResult : Result
{
public enum ExitCode
{
Success = 0,
Error = 1,
CriticalError = Common.Utils.ExitCode.CriticalError //ensure common value for final exit log mention
}
[JsonProperty(Order = 2)]
public ExitCode ResultCode { get; set; }
/// <summary>
/// List of Rules to pack as specified in pack command
/// </summary>
[JsonProperty(Order = 3)]
public List<Rule>? Rules { get; set; }
}
[JsonProperty(Order = 2)] public ExitCode ResultCode { get; set; }
/// <summary>
/// Used to combine validated rules into one json for ease in distribution of this
/// application
/// List of Rules to pack as specified in pack command
/// </summary>
public class PackRulesCommand
[JsonProperty(Order = 3)]
public List<Rule>? Rules { get; set; }
}
/// <summary>
/// Used to combine validated rules into one json
/// </summary>
public class PackRulesCommand
{
private readonly ILogger<PackRulesCommand> _logger;
private readonly ILoggerFactory? _loggerFactory;
private readonly PackRulesOptions _options;
public PackRulesCommand(PackRulesOptions opt, ILoggerFactory? loggerFactory = null)
{
private readonly PackRulesOptions _options;
private readonly ILogger<PackRulesCommand> _logger;
private readonly ILoggerFactory? _loggerFactory;
_options = opt;
_logger = loggerFactory?.CreateLogger<PackRulesCommand>() ?? NullLogger<PackRulesCommand>.Instance;
_loggerFactory = loggerFactory;
ConfigRules();
}
public PackRulesCommand(PackRulesOptions opt, ILoggerFactory? loggerFactory = null)
private void ConfigRules()
{
_logger.LogTrace("PackRulesCommand::ConfigRules");
if (string.IsNullOrEmpty(_options.CustomRulesPath) && !_options.PackEmbeddedRules)
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_NORULES_SPECIFIED));
}
/// <summary>
/// Intentional as no identified value in calling from DLL at this time
/// </summary>
/// <returns></returns>
public PackRulesResult GetResult()
{
_logger.LogTrace("PackRulesCommand::ConfigRules");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Pack Rules");
PackRulesResult packRulesResult = new()
{
_options = opt;
_logger = loggerFactory?.CreateLogger<PackRulesCommand>() ?? NullLogger<PackRulesCommand>.Instance;
_loggerFactory = loggerFactory;
ConfigRules();
}
AppVersion = Utils.GetVersionString()
};
private void ConfigRules()
try
{
_logger.LogTrace("PackRulesCommand::ConfigRules");
if (string.IsNullOrEmpty(_options.CustomRulesPath) && !_options.PackEmbeddedRules)
RulesVerifierOptions options = new()
{
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_NORULES_SPECIFIED));
}
}
/// <summary>
/// Intentional as no identified value in calling from DLL at this time
/// </summary>
/// <returns></returns>
public PackRulesResult GetResult()
{
_logger.LogTrace("PackRulesCommand::ConfigRules");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Pack Rules");
PackRulesResult packRulesResult = new()
{
AppVersion = Common.Utils.GetVersionString()
LoggerFactory = _loggerFactory,
LanguageSpecs = Languages.FromConfigurationFiles(_loggerFactory, _options.CustomCommentsPath,
_options.CustomLanguagesPath),
DisableRequireUniqueIds = _options.DisableRequireUniqueIds,
RequireMustMatch = _options.RequireMustMatch,
RequireMustNotMatch = _options.RequireMustNotMatch
};
try
{
RulesVerifierOptions options = new()
{
LoggerFactory = _loggerFactory,
LanguageSpecs = Languages.FromConfigurationFiles(_loggerFactory, _options.CustomCommentsPath, _options.CustomLanguagesPath)
};
RulesVerifier verifier = new(options);
RuleSet? ruleSet = _options.PackEmbeddedRules ? RuleSetUtils.GetDefaultRuleSet() : new RuleSet();
if (!string.IsNullOrEmpty(_options.CustomRulesPath))
{
ruleSet.AddDirectory(_options.CustomRulesPath);
}
RulesVerifierResult result = verifier.Verify(ruleSet);
if (!result.Verified)
{
throw new OpException(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_RESULTS_FAIL));
}
packRulesResult.Rules = result.CompiledRuleSet.GetAppInspectorRules().ToList();
packRulesResult.ResultCode = PackRulesResult.ExitCode.Success;
}
catch (OpException e)
{
_logger.LogError(e.Message);
//caught for CLI callers with final exit msg about checking log or throws for DLL callers
throw;
}
return packRulesResult;
RulesVerifier verifier = new(options);
var ruleSet = _options.PackEmbeddedRules ? RuleSetUtils.GetDefaultRuleSet() : new RuleSet();
if (!string.IsNullOrEmpty(_options.CustomRulesPath)) ruleSet.AddPath(_options.CustomRulesPath);
var result = verifier.Verify(ruleSet);
if (!result.Verified) throw new OpException(MsgHelp.GetString(MsgHelp.ID.VERIFY_RULES_RESULTS_FAIL));
packRulesResult.Rules = result.CompiledRuleSet.GetAppInspectorRules().ToList();
packRulesResult.ResultCode = PackRulesResult.ExitCode.Success;
}
catch (OpException e)
{
_logger.LogError(e.Message);
//caught for CLI callers with final exit msg about checking log or throws for DLL callers
throw;
}
return packRulesResult;
}
}

Просмотреть файл

@ -1,251 +1,250 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Commands
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.Commands;
public class TagDiffOptions
{
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
public IEnumerable<string> SourcePath1 { get; set; } = Array.Empty<string>();
public IEnumerable<string> SourcePath2 { get; set; } = Array.Empty<string>();
public TagTestType TestType { get; set; } = TagTestType.Equality;
public IEnumerable<string> FilePathExclusions { get; set; } = Array.Empty<string>();
public string? CustomRulesPath { get; set; }
public bool IgnoreDefaultRules { get; set; }
public int FileTimeOut { get; set; }
public int ProcessingTimeOut { get; set; }
public bool ScanUnknownTypes { get; set; }
public bool SingleThread { get; set; }
public IEnumerable<Confidence> ConfidenceFilters { get; set; } = new[] { Confidence.High, Confidence.Medium };
public class TagDiffOptions
public IEnumerable<Severity> SeverityFilters { get; set; } = new[]
{ Severity.Critical | Severity.Important | Severity.Moderate | Severity.BestPractice | Severity.ManualReview };
public string? CustomCommentsPath { get; set; }
public string? CustomLanguagesPath { get; set; }
public bool DisableCustomRuleValidation { get; set; }
public bool DisableRequireUniqueIds { get; set; }
/// <summary>
/// Return a success error code when no matches were found but operation was apparently successful. Useful for CI
/// scenarios
/// </summary>
public bool SuccessErrorCodeOnNoMatches { get; set; }
public bool RequireMustMatch { get; set; }
public bool RequireMustNotMatch { get; set; }
}
/// <summary>
/// Contains a tag that was detected missing in source1 or source2
/// </summary>
public class TagDiff
{
public enum DiffSource
{
public IEnumerable<string> SourcePath1 { get; set; } = Array.Empty<string>();
public IEnumerable<string> SourcePath2 { get; set; } = Array.Empty<string>();
public TagTestType TestType { get; set; } = TagTestType.Equality;
public IEnumerable<string> FilePathExclusions { get; set; } = Array.Empty<string>();
public string? CustomRulesPath { get; set; }
public bool IgnoreDefaultRules { get; set; }
public int FileTimeOut { get; set; }
public int ProcessingTimeOut { get; set; }
public bool ScanUnknownTypes { get; set; }
public bool SingleThread { get; set; }
public IEnumerable<Confidence> ConfidenceFilters { get; set; } = new Confidence[] { Confidence.High, Confidence.Medium };
public IEnumerable<Severity> SeverityFilters { get; set; } = new Severity[] { Severity.Critical | Severity.Important | Severity.Moderate | Severity.BestPractice | Severity.ManualReview };
public string? CustomCommentsPath { get; set; }
public string? CustomLanguagesPath { get; set; }
public bool DisableCustomRuleValidation { get; set; }
public bool DisableRequireUniqueIds { get; set; }
/// <summary>
/// Return a success error code when no matches were found but operation was apparently successful. Useful for CI scenarios
/// </summary>
public bool SuccessErrorCodeOnNoMatches { get; set; }
public bool RequireMustMatch { get; set; }
public bool RequireMustNotMatch { get; set; }
Source1 = 1,
Source2 = 2
}
/// <summary>
/// Contains a tag that was detected missing in source1 or source2
/// Tag value from rule used in comparison
/// </summary>
public class TagDiff
[JsonProperty(PropertyName = "tag")]
public string? Tag { get; set; }
/// <summary>
/// Source file (src1/src2) from the command option arguments
/// </summary>
[JsonProperty(PropertyName = "source")]
public DiffSource Source { get; set; }
}
/// <summary>
/// Result wrapping list of tags not found in one of the sources scanned
/// </summary>
public class TagDiffResult : Result
{
public enum ExitCode
{
public enum DiffSource
{
Source1 = 1,
Source2 = 2
}
/// <summary>
/// Tag value from rule used in comparison
/// </summary>
[JsonProperty(PropertyName = "tag")]
public string? Tag { get; set; }
/// <summary>
/// Source file (src1/src2) from the command option arguments
/// </summary>
[JsonProperty(PropertyName = "source")]
public DiffSource Source { get; set; }
TestPassed = 0,
TestFailed = 1,
CriticalError = Utils.ExitCode.CriticalError //ensure common value for final exit log mention
}
/// <summary>
/// Result wrapping list of tags not found in one of the sources scanned
/// List of tags which differ between src1 and src2
/// </summary>
public class TagDiffResult : Result
[JsonProperty(Order = 3, PropertyName = "tagDiffList")]
public List<TagDiff> TagDiffList;
public TagDiffResult()
{
public enum ExitCode
TagDiffList = new List<TagDiff>();
}
[JsonProperty(Order = 2, PropertyName = "resultCode")]
public ExitCode ResultCode { get; set; }
}
public enum TagTestType
{
Equality,
Inequality
}
/// <summary>
/// Used to compare two source paths and report tag differences
/// </summary>
public class TagDiffCommand
{
private readonly ILoggerFactory? _factory;
private readonly ILogger<TagDiffCommand> _logger;
private readonly TagDiffOptions? _options;
public TagDiffCommand(TagDiffOptions opt, ILoggerFactory? loggerFactory = null)
{
_options = opt;
_factory = loggerFactory;
_logger = loggerFactory?.CreateLogger<TagDiffCommand>() ?? NullLogger<TagDiffCommand>.Instance;
try
{
TestPassed = 0,
TestFailed = 1,
CriticalError = Utils.ExitCode.CriticalError //ensure common value for final exit log mention
ConfigSourceToScan();
}
[JsonProperty(Order = 2, PropertyName = "resultCode")]
public ExitCode ResultCode { get; set; }
/// <summary>
/// List of tags which differ between src1 and src2
/// </summary>
[JsonProperty(Order = 3, PropertyName = "tagDiffList")]
public List<TagDiff> TagDiffList;
public TagDiffResult()
catch (OpException e) //group error handling
{
TagDiffList = new List<TagDiff>();
_logger.LogError(e.Message);
throw;
}
}
public enum TagTestType { Equality, Inequality }
private void ConfigSourceToScan()
{
_logger.LogTrace("TagDiff::ConfigRules");
if ((!_options?.SourcePath1.Any() ?? true) || (!_options?.SourcePath2.Any() ?? true))
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_ARG_VALUE));
}
/// <summary>
/// Used to compare two source paths and report tag differences
/// Main entry from CLI
/// </summary>
public class TagDiffCommand
/// <returns></returns>
public TagDiffResult GetResult()
{
_logger.LogTrace("TagDiffCommand::Run");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Tag Diff");
private readonly TagDiffOptions? _options;
private readonly ILoggerFactory? _factory;
private readonly ILogger<TagDiffCommand> _logger;
TagDiffResult tagDiffResult = new() { AppVersion = Utils.GetVersionString() };
public TagDiffCommand(TagDiffOptions opt, ILoggerFactory? loggerFactory = null)
try
{
_options = opt;
_factory = loggerFactory;
_logger = loggerFactory?.CreateLogger<TagDiffCommand>() ?? NullLogger<TagDiffCommand>.Instance;
try
if (_options is null) throw new ArgumentNullException(nameof(_options));
AnalyzeCommand cmd1 = new(new AnalyzeOptions
{
ConfigSourceToScan();
}
catch (OpException e) //group error handling
SourcePath = _options.SourcePath1,
CustomRulesPath = _options.CustomRulesPath,
IgnoreDefaultRules = _options.IgnoreDefaultRules,
FilePathExclusions = _options.FilePathExclusions,
TagsOnly = true,
ConfidenceFilters = _options.ConfidenceFilters,
SeverityFilters = _options.SeverityFilters,
FileTimeOut = _options.FileTimeOut,
ProcessingTimeOut = _options.ProcessingTimeOut,
NoFileMetadata = true,
NoShowProgress = true,
ScanUnknownTypes = _options.ScanUnknownTypes,
SingleThread = _options.SingleThread,
CustomCommentsPath = _options.CustomCommentsPath,
CustomLanguagesPath = _options.CustomLanguagesPath,
DisableCustomRuleVerification = _options.DisableCustomRuleValidation,
DisableRequireUniqueIds = _options.DisableRequireUniqueIds,
SuccessErrorCodeOnNoMatches = _options.SuccessErrorCodeOnNoMatches,
RequireMustMatch = _options.RequireMustMatch,
RequireMustNotMatch = _options.RequireMustNotMatch
}, _factory);
AnalyzeCommand cmd2 = new(new AnalyzeOptions
{
_logger.LogError(e.Message);
throw;
SourcePath = _options.SourcePath2,
CustomRulesPath = _options.CustomRulesPath,
IgnoreDefaultRules = _options.IgnoreDefaultRules,
FilePathExclusions = _options.FilePathExclusions,
TagsOnly = true,
ConfidenceFilters = _options.ConfidenceFilters,
SeverityFilters = _options.SeverityFilters,
FileTimeOut = _options.FileTimeOut,
ProcessingTimeOut = _options.ProcessingTimeOut,
NoFileMetadata = true,
NoShowProgress = true,
ScanUnknownTypes = _options.ScanUnknownTypes,
SingleThread = _options.SingleThread,
CustomCommentsPath = _options.CustomCommentsPath,
CustomLanguagesPath = _options.CustomLanguagesPath,
DisableCustomRuleVerification = true, // Rules are already validated by the first command
SuccessErrorCodeOnNoMatches = _options.SuccessErrorCodeOnNoMatches
}, _factory);
var analyze1 = cmd1.GetResult();
var analyze2 = cmd2.GetResult();
//process results for each analyze call before comparing results
if (analyze1.ResultCode == AnalyzeResult.ExitCode.CriticalError)
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_CRITICAL_FILE_ERR,
string.Join(',', _options.SourcePath1)));
if (analyze2.ResultCode == AnalyzeResult.ExitCode.CriticalError)
{
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_CRITICAL_FILE_ERR,
string.Join(',', _options.SourcePath2)));
}
//compare tag results; assumed (result1&2 == AnalyzeCommand.ExitCode.Success)
var list1 = analyze1.Metadata.UniqueTags ?? new List<string>();
var list2 = analyze2.Metadata.UniqueTags ?? new List<string>();
var removed = list1.Except(list2);
var added = list2.Except(list1);
foreach (var add in added)
tagDiffResult.TagDiffList.Add(new TagDiff
{
Source = TagDiff.DiffSource.Source2,
Tag = add
});
foreach (var remove in removed)
tagDiffResult.TagDiffList.Add(new TagDiff
{
Source = TagDiff.DiffSource.Source1,
Tag = remove
});
if (tagDiffResult.TagDiffList.Count > 0)
tagDiffResult.ResultCode = _options.TestType == TagTestType.Inequality
? TagDiffResult.ExitCode.TestPassed
: TagDiffResult.ExitCode.TestFailed;
else
tagDiffResult.ResultCode = _options.TestType == TagTestType.Inequality
? TagDiffResult.ExitCode.TestFailed
: TagDiffResult.ExitCode.TestPassed;
return tagDiffResult;
}
private void ConfigSourceToScan()
catch (OpException e)
{
_logger.LogTrace("TagDiff::ConfigRules");
if ((!_options?.SourcePath1.Any() ?? true) || (!_options?.SourcePath2.Any() ?? true))
{
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_INVALID_ARG_VALUE));
}
}
/// <summary>
/// Main entry from CLI
/// </summary>
/// <returns></returns>
public TagDiffResult GetResult()
{
_logger.LogTrace("TagDiffCommand::Run");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Tag Diff");
TagDiffResult tagDiffResult = new() { AppVersion = Common.Utils.GetVersionString() };
try
{
if (_options is null)
{
throw new ArgumentNullException(nameof(_options));
}
AnalyzeCommand cmd1 = new(new AnalyzeOptions()
{
SourcePath = _options.SourcePath1,
CustomRulesPath = _options.CustomRulesPath,
IgnoreDefaultRules = _options.IgnoreDefaultRules,
FilePathExclusions = _options.FilePathExclusions,
TagsOnly = true,
ConfidenceFilters = _options.ConfidenceFilters,
SeverityFilters = _options.SeverityFilters,
FileTimeOut = _options.FileTimeOut,
ProcessingTimeOut = _options.ProcessingTimeOut,
NoFileMetadata = true,
NoShowProgress = true,
ScanUnknownTypes = _options.ScanUnknownTypes,
SingleThread = _options.SingleThread,
CustomCommentsPath = _options.CustomCommentsPath,
CustomLanguagesPath = _options.CustomLanguagesPath,
DisableCustomRuleVerification = _options.DisableCustomRuleValidation,
DisableRequireUniqueIds = _options.DisableRequireUniqueIds,
SuccessErrorCodeOnNoMatches = _options.SuccessErrorCodeOnNoMatches,
RequireMustMatch = _options.RequireMustMatch,
RequireMustNotMatch = _options.RequireMustNotMatch
}, _factory);
AnalyzeCommand cmd2 = new(new AnalyzeOptions()
{
SourcePath = _options.SourcePath2,
CustomRulesPath = _options.CustomRulesPath,
IgnoreDefaultRules = _options.IgnoreDefaultRules,
FilePathExclusions = _options.FilePathExclusions,
TagsOnly = true,
ConfidenceFilters = _options.ConfidenceFilters,
SeverityFilters = _options.SeverityFilters,
FileTimeOut = _options.FileTimeOut,
ProcessingTimeOut = _options.ProcessingTimeOut,
NoFileMetadata = true,
NoShowProgress = true,
ScanUnknownTypes = _options.ScanUnknownTypes,
SingleThread = _options.SingleThread,
CustomCommentsPath = _options.CustomCommentsPath,
CustomLanguagesPath = _options.CustomLanguagesPath,
DisableCustomRuleVerification = true, // Rules are already validated by the first command
SuccessErrorCodeOnNoMatches = _options.SuccessErrorCodeOnNoMatches
}, _factory);
AnalyzeResult analyze1 = cmd1.GetResult();
AnalyzeResult analyze2 = cmd2.GetResult();
//process results for each analyze call before comparing results
if (analyze1.ResultCode == AnalyzeResult.ExitCode.CriticalError)
{
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_CRITICAL_FILE_ERR, string.Join(',', _options.SourcePath1)));
}
else if (analyze2.ResultCode == AnalyzeResult.ExitCode.CriticalError)
{
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_CRITICAL_FILE_ERR, string.Join(',', _options.SourcePath2)));
}
else //compare tag results; assumed (result1&2 == AnalyzeCommand.ExitCode.Success)
{
var list1 = analyze1.Metadata.UniqueTags ?? new List<string>();
var list2 = analyze2.Metadata.UniqueTags ?? new List<string>();
var removed = list1.Except(list2);
var added = list2.Except(list1);
foreach (var add in added)
{
tagDiffResult.TagDiffList.Add(new TagDiff()
{
Source = TagDiff.DiffSource.Source2,
Tag = add
});
}
foreach (var remove in removed)
{
tagDiffResult.TagDiffList.Add(new TagDiff()
{
Source = TagDiff.DiffSource.Source1,
Tag = remove
});
}
if (tagDiffResult.TagDiffList.Count > 0)
{
tagDiffResult.ResultCode = _options.TestType == TagTestType.Inequality ? TagDiffResult.ExitCode.TestPassed : TagDiffResult.ExitCode.TestFailed;
}
else
{
tagDiffResult.ResultCode = _options.TestType == TagTestType.Inequality ? TagDiffResult.ExitCode.TestFailed : TagDiffResult.ExitCode.TestPassed;
}
return tagDiffResult;
}
}
catch (OpException e)
{
_logger.LogError(e.Message);
//caught for CLI callers with final exit msg about checking log or throws for DLL callers
throw;
}
_logger.LogError(e.Message);
//caught for CLI callers with final exit msg about checking log or throws for DLL callers
throw;
}
}
}

Просмотреть файл

@ -1,149 +1,142 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.ApplicationInspector.RulesEngine.OatExtensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.Commands
namespace Microsoft.ApplicationInspector.Commands;
public class VerifyRulesOptions
{
using Microsoft.ApplicationInspector.Common;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Collections.Generic;
using System.IO;
public bool VerifyDefaultRules { get; set; }
public string? CustomRulesPath { get; set; }
public string? CustomCommentsPath { get; set; }
public string? CustomLanguagesPath { get; set; }
public bool DisableRequireUniqueIds { get; set; }
public bool RequireMustMatch { get; set; }
public bool RequireMustNotMatch { get; set; }
}
public class VerifyRulesOptions
public class VerifyRulesResult : Result
{
[JsonConverter(typeof(StringEnumConverter))]
public enum ExitCode
{
public bool VerifyDefaultRules { get; set; }
public string? CustomRulesPath { get; set; }
public string? CustomCommentsPath { get; set; }
public string? CustomLanguagesPath { get; set; }
public bool DisableRequireUniqueIds { get; set; }
public bool RequireMustMatch { get; set; }
public bool RequireMustNotMatch { get; set; }
Verified = 0,
NotVerified = 1,
CriticalError = Utils.ExitCode.CriticalError
}
public class VerifyRulesResult : Result
public VerifyRulesResult()
{
[Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
public enum ExitCode
{
Verified = 0,
NotVerified = 1,
CriticalError = Utils.ExitCode.CriticalError
}
[JsonProperty(PropertyName ="resultCode")]
public ExitCode ResultCode { get; set; }
[JsonProperty(PropertyName ="ruleStatusList")]
public List<RuleStatus> RuleStatusList { get; set; }
[JsonIgnore] public IEnumerable<RuleStatus> Unverified => RuleStatusList.Where(x => !x.Verified);
public VerifyRulesResult()
{
RuleStatusList = new List<RuleStatus>();
}
RuleStatusList = new List<RuleStatus>();
}
[JsonProperty(PropertyName = "resultCode")]
public ExitCode ResultCode { get; set; }
[JsonProperty(PropertyName = "ruleStatusList")]
public List<RuleStatus> RuleStatusList { get; set; }
[JsonIgnore] public IEnumerable<RuleStatus> Unverified => RuleStatusList.Where(x => !x.Verified);
}
/// <summary>
/// Used to verify user custom ruleset. Default ruleset has no need for support outside of PackRulesCommand for
/// verification
/// since each build performs a verification already and the output is added to the binary manifest
/// </summary>
public class VerifyRulesCommand
{
private readonly ILogger<VerifyRulesCommand> _logger;
private readonly ILoggerFactory? _loggerFactory;
private readonly VerifyRulesOptions _options;
public VerifyRulesCommand(VerifyRulesOptions opt, ILoggerFactory? loggerFactory = null)
{
_options = opt;
_logger = loggerFactory?.CreateLogger<VerifyRulesCommand>() ?? NullLogger<VerifyRulesCommand>.Instance;
_loggerFactory = loggerFactory;
ConfigRules();
}
private void ConfigRules()
{
_logger.LogTrace("VerifyRulesCommand::ConfigRules");
if (!_options.VerifyDefaultRules && string.IsNullOrEmpty(_options.CustomRulesPath))
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_NORULES_SPECIFIED));
}
/// <summary>
/// Used to verify user custom ruleset. Default ruleset has no need for support outside of PackRulesCommand for verification
/// since each build performs a verification already and the output is added to the binary manifest
/// Option for DLL use as alternate to Run which only outputs a file to return results as string
/// CommandOption defaults will not have been set when used as DLL via CLI processing so some checks added
/// </summary>
public class VerifyRulesCommand
/// <returns>output results</returns>
public VerifyRulesResult GetResult()
{
private readonly VerifyRulesOptions _options;
private readonly ILogger<VerifyRulesCommand> _logger;
private readonly ILoggerFactory? _loggerFactory;
_logger.LogTrace("VerifyRulesCommand::Run");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Verify Rules");
public VerifyRulesCommand(VerifyRulesOptions opt, ILoggerFactory? loggerFactory = null)
VerifyRulesResult verifyRulesResult = new() { AppVersion = Utils.GetVersionString() };
try
{
_options = opt;
_logger = loggerFactory?.CreateLogger<VerifyRulesCommand>() ?? NullLogger<VerifyRulesCommand>.Instance;
_loggerFactory = loggerFactory;
ConfigRules();
}
private void ConfigRules()
{
_logger.LogTrace("VerifyRulesCommand::ConfigRules");
if (!_options.VerifyDefaultRules && string.IsNullOrEmpty(_options.CustomRulesPath))
var analyzer = new ApplicationInspectorAnalyzer();
RulesVerifierOptions options = new()
{
throw new OpException(MsgHelp.GetString(MsgHelp.ID.CMD_NORULES_SPECIFIED));
}
}
/// <summary>
/// Option for DLL use as alternate to Run which only outputs a file to return results as string
/// CommandOption defaults will not have been set when used as DLL via CLI processing so some checks added
/// </summary>
/// <returns>output results</returns>
public VerifyRulesResult GetResult()
{
_logger.LogTrace("VerifyRulesCommand::Run");
_logger.LogInformation(MsgHelp.GetString(MsgHelp.ID.CMD_RUNNING), "Verify Rules");
VerifyRulesResult verifyRulesResult = new() { AppVersion = Utils.GetVersionString() };
Analyzer = analyzer,
DisableRequireUniqueIds = _options.DisableRequireUniqueIds,
RequireMustMatch = _options.RequireMustMatch,
RequireMustNotMatch = _options.RequireMustNotMatch,
LoggerFactory = _loggerFactory,
LanguageSpecs = Languages.FromConfigurationFiles(_loggerFactory, _options.CustomCommentsPath,
_options.CustomLanguagesPath)
};
RulesVerifier verifier = new(options);
verifyRulesResult.ResultCode = VerifyRulesResult.ExitCode.Verified;
RuleSet? ruleSet = new(_loggerFactory);
if (_options.VerifyDefaultRules) ruleSet = RuleSetUtils.GetDefaultRuleSet(_loggerFactory);
try
{
var analyzer = new ApplicationInspectorAnalyzer();
RulesVerifierOptions options = new()
if (_options.CustomRulesPath != null)
{
Analyzer = analyzer,
DisableRequireUniqueIds = _options.DisableRequireUniqueIds,
RequireMustMatch = _options.RequireMustMatch,
RequireMustNotMatch = _options.RequireMustNotMatch,
LoggerFactory = _loggerFactory,
LanguageSpecs = Languages.FromConfigurationFiles(_loggerFactory, _options.CustomCommentsPath, _options.CustomLanguagesPath)
};
RulesVerifier verifier = new(options);
verifyRulesResult.ResultCode = VerifyRulesResult.ExitCode.Verified;
RuleSet? ruleSet = new(_loggerFactory);
if (_options.VerifyDefaultRules)
{
ruleSet = RuleSetUtils.GetDefaultRuleSet(_loggerFactory);
if (Directory.Exists(_options.CustomRulesPath))
ruleSet.AddDirectory(_options.CustomRulesPath);
else if (File.Exists(_options.CustomRulesPath)) ruleSet.AddFile(_options.CustomRulesPath);
}
try
{
if (_options.CustomRulesPath != null)
{
if (Directory.Exists(_options.CustomRulesPath))
{
ruleSet.AddDirectory(_options.CustomRulesPath);
}
else if (File.Exists(_options.CustomRulesPath))
{
ruleSet.AddFile(_options.CustomRulesPath);
}
}
}
catch(JsonException e)
{
_logger.LogError(e.Message);
verifyRulesResult.ResultCode = VerifyRulesResult.ExitCode.CriticalError;
return verifyRulesResult;
}
var verifyResult = verifier.Verify(ruleSet);
verifyRulesResult.RuleStatusList = verifyResult.RuleStatuses;
verifyRulesResult.ResultCode = verifyResult.Verified ? VerifyRulesResult.ExitCode.Verified : VerifyRulesResult.ExitCode.NotVerified;
}
catch (OpException e)
catch (JsonException e)
{
_logger.LogTrace(e.Message);
//caught for CLI callers with final exit msg about checking log or throws for DLL callers
throw;
_logger.LogError(e.Message);
verifyRulesResult.ResultCode = VerifyRulesResult.ExitCode.CriticalError;
return verifyRulesResult;
}
return verifyRulesResult;
var verifyResult = verifier.Verify(ruleSet);
verifyRulesResult.RuleStatusList = verifyResult.RuleStatuses;
verifyRulesResult.ResultCode = verifyResult.Verified
? VerifyRulesResult.ExitCode.Verified
: VerifyRulesResult.ExitCode.NotVerified;
}
catch (OpException e)
{
_logger.LogTrace(e.Message);
//caught for CLI callers with final exit msg about checking log or throws for DLL callers
throw;
}
return verifyRulesResult;
}
}

Просмотреть файл

@ -1,29 +1,28 @@
namespace Microsoft.ApplicationInspector.Commands
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.ApplicationInspector.Commands;
public class FileRecord
{
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
public class FileRecord
{
public string FileName { get; set; } = string.Empty;
public TimeSpan ScanTime { get; set; } = new TimeSpan();
public ScanState Status { get; set; } = ScanState.None;
public int NumFindings { get; set; } = 0;
public DateTime ModifyTime { get; set; } = DateTime.MinValue;
public DateTime CreateTime { get; set; } = DateTime.MinValue;
public DateTime AccessTime { get; set; } = DateTime.MinValue;
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ScanState
{
None,
Skipped,
TimedOut,
Analyzed,
Affected,
TimeOutSkipped,
Error
}
public string FileName { get; set; } = string.Empty;
public TimeSpan ScanTime { get; set; } = new();
public ScanState Status { get; set; } = ScanState.None;
public int NumFindings { get; set; } = 0;
public DateTime ModifyTime { get; set; } = DateTime.MinValue;
public DateTime CreateTime { get; set; } = DateTime.MinValue;
public DateTime AccessTime { get; set; } = DateTime.MinValue;
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ScanState
{
None,
Skipped,
TimedOut,
Analyzed,
Affected,
TimeOutSkipped,
Error
}

Просмотреть файл

@ -1,229 +1,237 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Commands
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ApplicationInspector.RulesEngine;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.Commands;
/// <summary>
/// Contains the analyze scanned meta data elements including rollup data for reporting purposes
/// </summary>
public class MetaData
{
using Microsoft.ApplicationInspector.RulesEngine;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
public MetaData(string applicationName, string sourcePath)
{
ApplicationName = applicationName;
SourcePath = sourcePath;
}
//simple properties
/// <summary>
/// Detected or derived project name
/// </summary>
[JsonProperty(PropertyName = "applicationName")]
public string? ApplicationName { get; set; }
/// <summary>
/// Contains the analyze scanned meta data elements including rollup data for reporting purposes
/// Source path provided argument
/// </summary>
public class MetaData
[JsonProperty(PropertyName = "sourcePath")]
public string? SourcePath { get; set; }
/// <summary>
/// Detected project source version
/// </summary>
[JsonProperty(PropertyName = "sourceVersion")]
public string? SourceVersion { get; set; }
/// <summary>
/// Detected source authors
/// </summary>
[JsonProperty(PropertyName = "authors")]
public string? Authors { get; set; }
/// <summary>
/// Detected source description
/// </summary>
[JsonProperty(PropertyName = "description")]
public string? Description { get; set; }
/// <summary>
/// Last modified date for source code scanned
/// </summary>
[JsonProperty(PropertyName = "lastUpdated")]
public string LastUpdated
{
//simple properties
/// <summary>
/// Detected or derived project name
/// </summary>
[JsonProperty(PropertyName = "applicationName")]
public string? ApplicationName { get; set; }
/// <summary>
/// Source path provided argument
/// </summary>
[JsonProperty(PropertyName = "sourcePath")]
public string? SourcePath { get; set; }
/// <summary>
/// Detected project source version
/// </summary>
[JsonProperty(PropertyName = "sourceVersion")]
public string? SourceVersion { get; set; }
/// <summary>
/// Detected source authors
/// </summary>
[JsonProperty(PropertyName = "authors")]
public string? Authors { get; set; }
/// <summary>
/// Detected source description
/// </summary>
[JsonProperty(PropertyName = "description")]
public string? Description { get; set; }
/// <summary>
/// Last modified date for source code scanned
/// </summary>
[JsonProperty(PropertyName = "lastUpdated")]
public string LastUpdated
{
get
{
if (Files.Any())
{
return Files.Select(x => x.ModifyTime).Max().ToString();
}
else
{
return DateTime.MinValue.ToString();
}
}
}
/// <summary>
/// Date of analyze scan
/// </summary>
[JsonProperty(PropertyName = "dateScanned")]
public string? DateScanned { get; set; }
/// <summary>
/// True if the overall analysis timed out
/// </summary>
[JsonProperty(PropertyName = "timedOut")]
public bool TimedOut { get; set; }
//stats
/// <summary>
/// Total number of files in source path
/// </summary>
[JsonProperty(PropertyName = "totalFiles")]
public int TotalFiles { get { return Files.Count; } }
/// <summary>
/// Total number of files Timed out on an individual timeout
/// </summary>
[JsonProperty(PropertyName = "filesTimedOut")]
public int FilesTimedOut { get { return Files.Count(x => x.Status == ScanState.TimedOut); } }
/// <summary>
/// Total number of files scanned
/// </summary>
[JsonProperty(PropertyName = "filesAnalyzed")]
public int FilesAnalyzed { get { return Files.Count(x => x.Status is ScanState.Analyzed or ScanState.Affected); } }
/// <summary>
/// Total number of skipped files based on supported formats
/// </summary>
[JsonProperty(PropertyName = "filesSkipped")]
public int FilesSkipped { get { return Files.Count(x => x.Status == ScanState.Skipped); } }
/// <summary>
/// Total number of skipped files based on overall timeout
/// </summary>
[JsonProperty(PropertyName = "filesTimeOutSkipped")]
public int FilesTimeOutSkipped { get { return Files.Count(x => x.Status == ScanState.TimeOutSkipped); } }
/// <summary>
/// Total files with at least one result
/// </summary>
[JsonProperty(PropertyName = "filesAffected")]
public int FilesAffected { get { return Files.Count(x => x.Status == ScanState.Affected); } }
/// <summary>
/// Number of files which encountered an error when processing other than timing out.
/// </summary>
[JsonProperty(PropertyName = "filesErrored")]
public int FileErrored { get { return Files.Count(x => x.Status == ScanState.Error); } }
/// <summary>
/// Total matches with supplied argument settings
/// </summary>
[JsonProperty(PropertyName = "totalMatchesCount")]
public int TotalMatchesCount { get { return Matches?.Count ?? 0; } }
/// <summary>
/// Total unique matches by Rule Id
/// </summary>
[JsonProperty(PropertyName = "uniqueMatchesCount")]
public int UniqueMatchesCount
get
{
get
{
return Matches?.Select(x => x.RuleId).Distinct().Count() ?? 0;
}
}
/// <summary>
/// List of detected package types
/// </summary>
[JsonProperty(PropertyName = "packageTypes")]
public List<string>? PackageTypes { get; set; } = new List<string>();
/// <summary>
/// List of detected application types
/// </summary>
[JsonProperty(PropertyName = "appTypes")]
public List<string>? AppTypes { get; set; } = new List<string>();
/// <summary>
/// List of detected unique tags
/// </summary>
[JsonProperty(PropertyName = "uniqueTags")]
public List<string> UniqueTags { get; set; } = new List<string>();
/// <summary>
/// List of detected unique code dependency includes
/// </summary>
[JsonProperty(PropertyName = "uniqueDependencies")]
public List<string>? UniqueDependencies { get; set; } = new List<string>();
/// <summary>
/// List of detected output types
/// </summary>
[JsonProperty(PropertyName = "outputs")]
public List<string>? Outputs { get; set; } = new List<string>();
/// <summary>
/// List of detected target types
/// </summary>
[JsonProperty(PropertyName = "targets")]
public List<string> Targets { get; set; } = new List<string>();
/// <summary>
/// List of detected OS targets
/// </summary>
[JsonProperty(PropertyName = "OSTargets")]
public List<string>? OSTargets { get; set; } = new List<string>();
/// <summary>
/// LIst of detected file types (extension based)
/// </summary>
[JsonProperty(PropertyName = "fileExtensions")]
public List<string>? FileExtensions { get; set; } = new List<string>();
/// <summary>
/// List of detected cloud host targets
/// </summary>
[JsonProperty(PropertyName = "cloudTargets")]
public List<string>? CloudTargets { get; set; } = new List<string>();
/// <summary>
/// List of detected cpu targets
/// </summary>
[JsonProperty(PropertyName = "CPUTargets")]
public List<string>? CPUTargets { get; set; } = new List<string>();
/// <summary>
/// List of detected programming languages used and count of files
/// </summary>
[JsonProperty(PropertyName = "languages")]
public IDictionary<string, int>? Languages { get; set; } //unable to init here for constr arg
/// <summary>
/// List of detected tag counters i.e. metrics
/// </summary>
[JsonProperty(PropertyName = "tagCounters")]
public List<MetricTagCounter>? TagCounters { get; set; } = new List<MetricTagCounter>();
/// <summary>
/// List of detailed MatchRecords from scan
/// </summary>
[JsonProperty(PropertyName = "detailedMatchList")]
public List<MatchRecord> Matches { get; set; } = new List<MatchRecord>();
[JsonProperty(PropertyName = "filesInformation")]
public List<FileRecord> Files { get; set; } = new List<FileRecord>();
public MetaData(string applicationName, string sourcePath)
{
ApplicationName = applicationName;
SourcePath = sourcePath;
if (Files.Any())
return Files.Select(x => x.ModifyTime).Max().ToString();
return DateTime.MinValue.ToString();
}
}
/// <summary>
/// Date of analyze scan
/// </summary>
[JsonProperty(PropertyName = "dateScanned")]
public string? DateScanned { get; set; }
/// <summary>
/// True if the overall analysis timed out
/// </summary>
[JsonProperty(PropertyName = "timedOut")]
public bool TimedOut { get; set; }
//stats
/// <summary>
/// Total number of files in source path
/// </summary>
[JsonProperty(PropertyName = "totalFiles")]
public int TotalFiles => Files.Count;
/// <summary>
/// Total number of files Timed out on an individual timeout
/// </summary>
[JsonProperty(PropertyName = "filesTimedOut")]
public int FilesTimedOut
{
get { return Files.Count(x => x.Status == ScanState.TimedOut); }
}
/// <summary>
/// Total number of files scanned
/// </summary>
[JsonProperty(PropertyName = "filesAnalyzed")]
public int FilesAnalyzed
{
get { return Files.Count(x => x.Status is ScanState.Analyzed or ScanState.Affected); }
}
/// <summary>
/// Total number of skipped files based on supported formats
/// </summary>
[JsonProperty(PropertyName = "filesSkipped")]
public int FilesSkipped
{
get { return Files.Count(x => x.Status == ScanState.Skipped); }
}
/// <summary>
/// Total number of skipped files based on overall timeout
/// </summary>
[JsonProperty(PropertyName = "filesTimeOutSkipped")]
public int FilesTimeOutSkipped
{
get { return Files.Count(x => x.Status == ScanState.TimeOutSkipped); }
}
/// <summary>
/// Total files with at least one result
/// </summary>
[JsonProperty(PropertyName = "filesAffected")]
public int FilesAffected
{
get { return Files.Count(x => x.Status == ScanState.Affected); }
}
/// <summary>
/// Number of files which encountered an error when processing other than timing out.
/// </summary>
[JsonProperty(PropertyName = "filesErrored")]
public int FileErrored
{
get { return Files.Count(x => x.Status == ScanState.Error); }
}
/// <summary>
/// Total matches with supplied argument settings
/// </summary>
[JsonProperty(PropertyName = "totalMatchesCount")]
public int TotalMatchesCount => Matches?.Count ?? 0;
/// <summary>
/// Total unique matches by Rule Id
/// </summary>
[JsonProperty(PropertyName = "uniqueMatchesCount")]
public int UniqueMatchesCount
{
get { return Matches?.Select(x => x.RuleId).Distinct().Count() ?? 0; }
}
/// <summary>
/// List of detected package types
/// </summary>
[JsonProperty(PropertyName = "packageTypes")]
public List<string>? PackageTypes { get; set; } = new();
/// <summary>
/// List of detected application types
/// </summary>
[JsonProperty(PropertyName = "appTypes")]
public List<string>? AppTypes { get; set; } = new();
/// <summary>
/// List of detected unique tags
/// </summary>
[JsonProperty(PropertyName = "uniqueTags")]
public List<string> UniqueTags { get; set; } = new();
/// <summary>
/// List of detected unique code dependency includes
/// </summary>
[JsonProperty(PropertyName = "uniqueDependencies")]
public List<string>? UniqueDependencies { get; set; } = new();
/// <summary>
/// List of detected output types
/// </summary>
[JsonProperty(PropertyName = "outputs")]
public List<string>? Outputs { get; set; } = new();
/// <summary>
/// List of detected target types
/// </summary>
[JsonProperty(PropertyName = "targets")]
public List<string> Targets { get; set; } = new();
/// <summary>
/// List of detected OS targets
/// </summary>
[JsonProperty(PropertyName = "OSTargets")]
public List<string>? OSTargets { get; set; } = new();
/// <summary>
/// LIst of detected file types (extension based)
/// </summary>
[JsonProperty(PropertyName = "fileExtensions")]
public List<string>? FileExtensions { get; set; } = new();
/// <summary>
/// List of detected cloud host targets
/// </summary>
[JsonProperty(PropertyName = "cloudTargets")]
public List<string>? CloudTargets { get; set; } = new();
/// <summary>
/// List of detected cpu targets
/// </summary>
[JsonProperty(PropertyName = "CPUTargets")]
public List<string>? CPUTargets { get; set; } = new();
/// <summary>
/// List of detected programming languages used and count of files
/// </summary>
[JsonProperty(PropertyName = "languages")]
public IDictionary<string, int>? Languages { get; set; } //unable to init here for constr arg
/// <summary>
/// List of detected tag counters i.e. metrics
/// </summary>
[JsonProperty(PropertyName = "tagCounters")]
public List<MetricTagCounter>? TagCounters { get; set; } = new();
/// <summary>
/// List of detailed MatchRecords from scan
/// </summary>
[JsonProperty(PropertyName = "detailedMatchList")]
public List<MatchRecord> Matches { get; set; } = new();
[JsonProperty(PropertyName = "filesInformation")]
public List<FileRecord> Files { get; set; } = new();
}

Просмотреть файл

@ -1,361 +1,310 @@
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
namespace Microsoft.ApplicationInspector.Commands
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ApplicationInspector.RulesEngine;
namespace Microsoft.ApplicationInspector.Commands;
/// <summary>
/// Provides utilty help specific to aggregating metadata from analyze cmd matches while isolating scanned data from
/// that process
/// Hides complexity i.e. threaded scanning so that caller gets simple list
/// <T> that is presorted and consistent each scan
/// </summary>
public class MetaDataHelper
{
using Microsoft.ApplicationInspector.RulesEngine;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
public MetaDataHelper(string sourcePath)
{
if (!sourcePath.Contains(',')) sourcePath = Path.GetFullPath(sourcePath); //normalize for .\ and similar
Metadata = new MetaData(sourcePath, sourcePath);
}
//visible to callers i.e. AnalyzeCommand
internal ConcurrentDictionary<string, byte> PackageTypes { get; set; } = new();
internal ConcurrentDictionary<string, byte> FileExtensions { get; set; } = new();
internal ConcurrentDictionary<string, byte> UniqueDependencies { get; set; } = new();
private ConcurrentDictionary<string, byte> AppTypes { get; } = new();
internal ConcurrentDictionary<string, int> UniqueTags { get; set; } = new();
private ConcurrentDictionary<string, byte> Outputs { get; } = new();
private ConcurrentDictionary<string, byte> Targets { get; } = new();
private ConcurrentDictionary<string, byte> CPUTargets { get; } = new();
private ConcurrentDictionary<string, byte> CloudTargets { get; } = new();
private ConcurrentDictionary<string, byte> OSTargets { get; } = new();
private ConcurrentDictionary<string, MetricTagCounter> TagCounters { get; } = new();
private ConcurrentDictionary<string, int> Languages { get; } = new();
internal ConcurrentBag<MatchRecord> Matches { get; set; } = new();
internal ConcurrentBag<FileRecord> Files { get; set; } = new();
public int UniqueTagsCount => UniqueTags.Keys.Count;
internal MetaData Metadata { get; set; }
internal bool HasFindings => Matches.Any() || TagCounters.Any() || UniqueTags.Any();
/// <summary>
/// Provides utilty help specific to aggregating metadata from analyze cmd matches while isolating scanned data from that process
/// Hides complexity i.e. threaded scanning so that caller gets simple list<T> that is presorted and consistent each scan
/// Assist in aggregating reporting properties of matches as they are added
/// Keeps helpers isolated from MetaData class which is used as a result object to keep pure
/// </summary>
public class MetaDataHelper
/// <param name="matchRecord"></param>
public void AddTagsFromMatchRecord(MatchRecord matchRecord)
{
//visible to callers i.e. AnalyzeCommand
internal ConcurrentDictionary<string, byte> PackageTypes { get; set; } = new ConcurrentDictionary<string, byte>();
internal ConcurrentDictionary<string, byte> FileExtensions { get; set; } = new ConcurrentDictionary<string, byte>();
internal ConcurrentDictionary<string, byte> UniqueDependencies { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> AppTypes { get; set; } = new ConcurrentDictionary<string, byte>();
internal ConcurrentDictionary<string, int> UniqueTags { get; set; } = new ConcurrentDictionary<string, int>();
private ConcurrentDictionary<string, byte> Outputs { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> Targets { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> CPUTargets { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> CloudTargets { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, byte> OSTargets { get; set; } = new ConcurrentDictionary<string, byte>();
private ConcurrentDictionary<string, MetricTagCounter> TagCounters { get; set; } = new ConcurrentDictionary<string, MetricTagCounter>();
private ConcurrentDictionary<string, int> Languages { get; set; } = new ConcurrentDictionary<string, int>();
internal ConcurrentBag<MatchRecord> Matches { get; set; } = new ConcurrentBag<MatchRecord>();
internal ConcurrentBag<FileRecord> Files { get; set; } = new ConcurrentBag<FileRecord>();
public int UniqueTagsCount { get { return UniqueTags.Keys.Count; } }
internal MetaData Metadata { get; set; }
internal bool HasFindings
{
get
//special handling for standard characteristics in report
foreach (var tag in matchRecord.Tags ?? Array.Empty<string>())
switch (tag)
{
return Matches.Any() || TagCounters.Any() || UniqueTags.Any();
case "Metadata.Application.Author":
case "Metadata.Application.Publisher":
Metadata.Authors = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Description":
Metadata.Description = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Name":
Metadata.ApplicationName = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Version":
Metadata.SourceVersion = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Target.Processor":
CPUTargets.TryAdd(ExtractValue(matchRecord.Sample).ToLower(), 0);
break;
case "Metadata.Application.Output.Type":
Outputs.TryAdd(ExtractValue(matchRecord.Sample).ToLower(), 0);
break;
case "Dependency.SourceInclude":
return; //design to keep noise out of detailed match list
default:
if (tag.Split('.').Contains("Metric"))
_ = TagCounters.TryAdd(tag, new MetricTagCounter
{
Tag = tag
});
else if (tag.Contains(".Platform.OS"))
OSTargets.TryAdd(tag[(tag.LastIndexOf('.', tag.Length - 1) + 1)..], 0);
else if (tag.Contains("CloudServices.Hosting"))
CloudTargets.TryAdd(tag[(tag.LastIndexOf('.', tag.Length - 1) + 1)..], 0);
break;
}
//Special handling; attempt to detect app types...review for multiple pattern rule limitation
var solutionType = DetectSolutionType(matchRecord);
if (!string.IsNullOrEmpty(solutionType)) AppTypes.TryAdd(solutionType, 0);
var CounterOnlyTagSet = false;
var selected = matchRecord.Tags is not null
? TagCounters.Where(x => matchRecord.Tags.Any(y => y.Contains(x.Value.Tag ?? "")))
: new Dictionary<string, MetricTagCounter>();
foreach (var select in selected)
{
CounterOnlyTagSet = true;
select.Value.IncrementCount();
}
public MetaDataHelper(string sourcePath)
{
if (!sourcePath.Contains(','))
{
sourcePath = Path.GetFullPath(sourcePath);//normalize for .\ and similar
}
Metadata = new MetaData(sourcePath, sourcePath);
}
/// <summary>
/// Assist in aggregating reporting properties of matches as they are added
/// Keeps helpers isolated from MetaData class which is used as a result object to keep pure
/// </summary>
/// <param name="matchRecord"></param>
public void AddTagsFromMatchRecord(MatchRecord matchRecord)
{
//special handling for standard characteristics in report
//omit adding if ther a counter metric tag
if (!CounterOnlyTagSet)
//update list of unique tags as we go
foreach (var tag in matchRecord.Tags ?? Array.Empty<string>())
if (!UniqueTags.TryAdd(tag, 1))
UniqueTags[tag]++;
}
/// <summary>
/// Assist in aggregating reporting properties of matches as they are added
/// Keeps helpers isolated from MetaData class which is used as a result object to keep pure
/// </summary>
/// <param name="matchRecord"></param>
public void AddMatchRecord(MatchRecord matchRecord)
{
AddTagsFromMatchRecord(matchRecord);
var nonCounters = matchRecord.Tags?.Where(x => !TagCounters.Any(y => y.Key == x)) ?? Array.Empty<string>();
//omit adding if it if all the tags were counters
if (nonCounters.Any()) Matches.Add(matchRecord);
}
/// <summary>
/// Transfer concurrent data from scan to analyze result with sorted, simplier types for callers
/// </summary>
public void PrepareReport()
{
Metadata.CPUTargets = CPUTargets.Keys.ToList();
Metadata.AppTypes = AppTypes.Keys.ToList();
Metadata.OSTargets = OSTargets.Keys.ToList();
Metadata.UniqueDependencies = UniqueDependencies.Keys.ToList();
Metadata.UniqueTags = UniqueTags.Keys.ToList();
Metadata.CloudTargets = CloudTargets.Keys.ToList();
Metadata.PackageTypes = PackageTypes.Keys.ToList();
Metadata.FileExtensions = FileExtensions.Keys.ToList();
Metadata.Outputs = Outputs.Keys.ToList();
Metadata.Targets = Targets.Keys.ToList();
Metadata.Files = Files.ToList();
Metadata.Matches = Matches.ToList();
Metadata.CPUTargets.Sort();
Metadata.AppTypes.Sort();
Metadata.OSTargets.Sort();
Metadata.UniqueDependencies.Sort();
Metadata.UniqueTags.Sort();
Metadata.CloudTargets.Sort();
Metadata.PackageTypes.Sort();
Metadata.FileExtensions.Sort();
Metadata.Outputs.Sort();
Metadata.Targets.Sort();
Metadata.Languages = new SortedDictionary<string, int>(Languages);
foreach (var metricTagCounter in TagCounters.Values) Metadata.TagCounters?.Add(metricTagCounter);
}
/// <summary>
/// Defined here to isolate MetaData from data processing methods and keep as pure data
/// </summary>
/// <param name="language"></param>
public void AddLanguage(string language)
{
Languages.AddOrUpdate(language, 1, (language, count) => count + 1);
}
/// <summary>
/// Initial best guess to deduce project name; if scanned metadata from project solution value is replaced later
/// </summary>
/// <param name="sourcePath"></param>
/// <returns></returns>
private string GetDefaultProjectName(string sourcePath)
{
var applicationName = string.Empty;
if (Directory.Exists(sourcePath))
{
if (sourcePath != string.Empty)
{
switch (tag)
if (sourcePath[^1] == Path.DirectorySeparatorChar) //in case path ends with dir separator; remove
applicationName = sourcePath.Trim(Path.DirectorySeparatorChar);
if (applicationName.LastIndexOf(Path.DirectorySeparatorChar) is int idx && idx != -1)
applicationName = applicationName[idx..].Trim();
}
}
else
{
applicationName = Path.GetFileNameWithoutExtension(sourcePath);
}
return applicationName;
}
/// <summary>
/// Attempt to map application type tags or file type or language to identify
/// WebApplications, Windows Services, Client Apps, WebServices, Azure Functions etc.
/// </summary>
/// <param name="match"></param>
public string DetectSolutionType(MatchRecord match)
{
var result = "";
if (match.Tags is not null && match.Tags.Any(s => s.Contains("Application.Type")))
foreach (var tag in match.Tags ?? Array.Empty<string>())
{
var index = tag.IndexOf("Application.Type");
if (-1 != index)
{
case "Metadata.Application.Author":
case "Metadata.Application.Publisher":
Metadata.Authors = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Description":
Metadata.Description = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Name":
Metadata.ApplicationName = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Version":
Metadata.SourceVersion = ExtractValue(matchRecord.Sample);
break;
case "Metadata.Application.Target.Processor":
CPUTargets.TryAdd(ExtractValue(matchRecord.Sample).ToLower(), 0);
break;
case "Metadata.Application.Output.Type":
Outputs.TryAdd(ExtractValue(matchRecord.Sample).ToLower(), 0);
break;
case "Dependency.SourceInclude":
return; //design to keep noise out of detailed match list
default:
if (tag.Split('.').Contains("Metric"))
{
_ = TagCounters.TryAdd(tag, new MetricTagCounter()
result = tag[(index + 17)..];
break;
}
}
else
switch (match.FileName)
{
case "web.config":
result = "Web.Application";
break;
case "app.config":
result = ".NETclient";
break;
default:
switch (Path.GetExtension(match.FileName))
{
case ".cshtml":
result = "Web.Application";
break;
case ".htm":
case ".html":
case ".js":
case ".ts":
result = "Web.Application";
break;
case "powershell":
case "shellscript":
case "wincmdscript":
result = "script";
break;
default:
switch (match.Language)
{
Tag = tag
});
}
else if (tag.Contains(".Platform.OS"))
{
OSTargets.TryAdd(tag[(tag.LastIndexOf('.', tag.Length - 1) + 1)..], 0);
}
else if (tag.Contains("CloudServices.Hosting"))
{
CloudTargets.TryAdd(tag[(tag.LastIndexOf('.', tag.Length - 1) + 1)..], 0);
}
break;
}
}
case "ruby":
case "perl":
case "php":
result = "Web.Application";
break;
}
//Special handling; attempt to detect app types...review for multiple pattern rule limitation
string solutionType = DetectSolutionType(matchRecord);
if (!string.IsNullOrEmpty(solutionType))
{
AppTypes.TryAdd(solutionType, 0);
}
bool CounterOnlyTagSet = false;
var selected = matchRecord.Tags is not null ? TagCounters.Where(x => matchRecord.Tags.Any(y => y.Contains(x.Value.Tag ?? ""))) : new Dictionary<string, MetricTagCounter>();
foreach (var select in selected)
{
CounterOnlyTagSet = true;
select.Value.IncrementCount();
}
//omit adding if ther a counter metric tag
if (!CounterOnlyTagSet)
{
//update list of unique tags as we go
foreach (string tag in matchRecord.Tags ?? Array.Empty<string>())
{
if (!UniqueTags.TryAdd(tag, 1))
{
UniqueTags[tag]++;
break;
}
}
}
}
/// <summary>
/// Assist in aggregating reporting properties of matches as they are added
/// Keeps helpers isolated from MetaData class which is used as a result object to keep pure
/// </summary>
/// <param name="matchRecord"></param>
public void AddMatchRecord(MatchRecord matchRecord)
break;
}
return result.ToLower();
}
private string ExtractValue(string s)
{
if (s.ToLower().Contains("</", StringComparison.Ordinal))
return ExtractXMLValue(s);
if (s.ToLower().Contains('<'))
return ExtractXMLValueMultiLine(s);
if (s.ToLower().Contains(':')) return ExtractJSONValue(s);
return s;
}
private static string ExtractJSONValue(string s)
{
var parts = s.Split(':');
if (parts.Length == 2) return parts[1].Replace("\"", "").Trim();
return s;
}
private string ExtractXMLValue(string s)
{
var firstTag = s.IndexOf(">");
if (firstTag > -1 && firstTag < s.Length - 1)
{
AddTagsFromMatchRecord(matchRecord);
var nonCounters = matchRecord.Tags?.Where(x => !TagCounters.Any(y => y.Key == x)) ?? Array.Empty<string>();
//omit adding if it if all the tags were counters
if (nonCounters.Any())
{
Matches.Add(matchRecord);
}
var endTag = s.IndexOf("</", firstTag);
if (endTag > -1) return s[(firstTag + 1)..endTag];
}
return s;
}
/// <summary>
/// Transfer concurrent data from scan to analyze result with sorted, simplier types for callers
/// </summary>
public void PrepareReport()
{
Metadata.CPUTargets = CPUTargets.Keys.ToList();
Metadata.AppTypes = AppTypes.Keys.ToList();
Metadata.OSTargets = OSTargets.Keys.ToList();
Metadata.UniqueDependencies = UniqueDependencies.Keys.ToList();
Metadata.UniqueTags = UniqueTags.Keys.ToList();
Metadata.CloudTargets = CloudTargets.Keys.ToList();
Metadata.PackageTypes = PackageTypes.Keys.ToList();
Metadata.FileExtensions = FileExtensions.Keys.ToList();
Metadata.Outputs = Outputs.Keys.ToList();
Metadata.Targets = Targets.Keys.ToList();
Metadata.Files = Files.ToList();
Metadata.Matches = Matches.ToList();
Metadata.CPUTargets.Sort();
Metadata.AppTypes.Sort();
Metadata.OSTargets.Sort();
Metadata.UniqueDependencies.Sort();
Metadata.UniqueTags.Sort();
Metadata.CloudTargets.Sort();
Metadata.PackageTypes.Sort();
Metadata.FileExtensions.Sort();
Metadata.Outputs.Sort();
Metadata.Targets.Sort();
Metadata.Languages = new SortedDictionary<string,int>(Languages);
foreach (MetricTagCounter metricTagCounter in TagCounters.Values)
{
Metadata.TagCounters?.Add(metricTagCounter);
}
}
/// <summary>
/// Defined here to isolate MetaData from data processing methods and keep as pure data
/// </summary>
/// <param name="language"></param>
public void AddLanguage(string language)
{
Languages.AddOrUpdate(language, 1, (language, count) => count + 1);
}
/// <summary>
/// Initial best guess to deduce project name; if scanned metadata from project solution value is replaced later
/// </summary>
/// <param name="sourcePath"></param>
/// <returns></returns>
private string GetDefaultProjectName(string sourcePath)
{
string applicationName = string.Empty;
if (Directory.Exists(sourcePath))
{
if (sourcePath != string.Empty)
{
if (sourcePath[^1] == Path.DirectorySeparatorChar) //in case path ends with dir separator; remove
{
applicationName = sourcePath.Trim(Path.DirectorySeparatorChar);
}
if (applicationName.LastIndexOf(Path.DirectorySeparatorChar) is int idx && idx != -1)
{
applicationName = applicationName[idx..].Trim();
}
}
}
else
{
applicationName = Path.GetFileNameWithoutExtension(sourcePath);
}
return applicationName;
}
/// <summary>
/// Attempt to map application type tags or file type or language to identify
/// WebApplications, Windows Services, Client Apps, WebServices, Azure Functions etc.
/// </summary>
/// <param name="match"></param>
public string DetectSolutionType(MatchRecord match)
{
string result = "";
if (match.Tags is not null && match.Tags.Any(s => s.Contains("Application.Type")))
{
foreach (string tag in match.Tags ?? Array.Empty<string>())
{
int index = tag.IndexOf("Application.Type");
if (-1 != index)
{
result = tag[(index + 17)..];
break;
}
}
}
else
{
switch (match.FileName)
{
case "web.config":
result = "Web.Application";
break;
case "app.config":
result = ".NETclient";
break;
default:
switch (Path.GetExtension(match.FileName))
{
case ".cshtml":
result = "Web.Application";
break;
case ".htm":
case ".html":
case ".js":
case ".ts":
result = "Web.Application";
break;
case "powershell":
case "shellscript":
case "wincmdscript":
result = "script";
break;
default:
switch (match.Language)
{
case "ruby":
case "perl":
case "php":
result = "Web.Application";
break;
}
break;
}
break;
}
}
return result.ToLower();
}
private string ExtractValue(string s)
{
if (s.ToLower().Contains("</", StringComparison.Ordinal))
{
return ExtractXMLValue(s);
}
else if (s.ToLower().Contains('<'))
{
return ExtractXMLValueMultiLine(s);
}
else if (s.ToLower().Contains(':'))
{
return ExtractJSONValue(s);
}
return s;
}
private static string ExtractJSONValue(string s)
{
var parts = s.Split(':');
if (parts.Length == 2)
{
return parts[1].Replace("\"", "").Trim();
}
return s;
}
private string ExtractXMLValue(string s)
{
int firstTag = s.IndexOf(">");
if (firstTag > -1 && firstTag < s.Length - 1)
{
int endTag = s.IndexOf("</", firstTag);
if (endTag > -1)
{
return s[(firstTag + 1)..endTag];
}
}
return s;
}
private string ExtractXMLValueMultiLine(string s)
{
int firstTag = s.IndexOf(">");
if (firstTag > -1 && firstTag < s.Length - 1)
{
return s[(firstTag + 1)..];
}
return s;
}
private string ExtractXMLValueMultiLine(string s)
{
var firstTag = s.IndexOf(">");
if (firstTag > -1 && firstTag < s.Length - 1) return s[(firstTag + 1)..];
return s;
}
}

Просмотреть файл

@ -1,27 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.ApplicationInspector.Commands
using System.Threading;
using Newtonsoft.Json;
namespace Microsoft.ApplicationInspector.Commands;
/// <summary>
/// Used for metric results not included in match details reported other than count of instances for given tag i.e.
/// Metrics.[value].[value]
/// </summary>
public class MetricTagCounter
{
using Newtonsoft.Json;
using System.Threading;
private int _count;
/// <summary>
/// Used for metric results not included in match details reported other than count of instances for given tag i.e. Metrics.[value].[value]
/// </summary>
public class MetricTagCounter
[JsonProperty(PropertyName = "tag")] public string? Tag { get; set; }
[JsonProperty(PropertyName = "count")] public int Count => _count;
internal void IncrementCount(int amount = 1)
{
[JsonProperty(PropertyName = "tag")]
public string? Tag { get; set; }
[JsonProperty(PropertyName = "count")]
public int Count { get { return _count; } }
private int _count = 0;
internal void IncrementCount(int amount = 1)
{
Interlocked.Add(ref _count, amount);
}
Interlocked.Add(ref _count, amount);
}
}

Просмотреть файл

@ -3,12 +3,12 @@
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishDir>bin\Release\netcoreapp3.0\publish\</PublishDir>
<SelfContained>false</SelfContained>
</PropertyGroup>
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishDir>bin\Release\netcoreapp3.0\publish\</PublishDir>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

1
AppInspector/Properties/Resources.Designer.cs сгенерированный
Просмотреть файл

@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

Просмотреть файл

@ -1,316 +1,326 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root"
xmlns="">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ANALYZE_COMPRESSED_ERROR" xml:space="preserve">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="ANALYZE_COMPRESSED_ERROR" xml:space="preserve">
<value>Problem while decompressing {0}: Unzip and try running on uncompressed files</value>
</data>
<data name="ANALYZE_COMPRESSED_FILESIZE_WARN" xml:space="preserve">
<data name="ANALYZE_COMPRESSED_FILESIZE_WARN" xml:space="preserve">
<value>Decompressing may take longer for larger files</value>
</data>
<data name="ANALYZE_COMPRESSED_FILETYPE" xml:space="preserve">
<data name="ANALYZE_COMPRESSED_FILETYPE" xml:space="preserve">
<value>compressed</value>
</data>
<data name="ANALYZE_COMPRESSED_PROCESSING" xml:space="preserve">
<data name="ANALYZE_COMPRESSED_PROCESSING" xml:space="preserve">
<value>Decompressing files...</value>
</data>
<data name="ANALYZE_EXCLUDED_TYPE_SKIPPED" xml:space="preserve">
<data name="ANALYZE_EXCLUDED_TYPE_SKIPPED" xml:space="preserve">
<value>File skipped: Name or path in exclude list. {0}</value>
</data>
<data name="ANALYZE_EXCLUDED_BINARY" xml:space="preserve">
<data name="ANALYZE_EXCLUDED_BINARY" xml:space="preserve">
<value>File skipped: First 100 bytes appear to indicate this is a binary file. {0}</value>
</data>
<data name="ANALYZE_FILESIZE_SKIPPED" xml:space="preserve">
<data name="ANALYZE_FILESIZE_SKIPPED" xml:space="preserve">
<value>File skipped: File size is too large. {0}</value>
</data>
<data name="ANALYZE_FILES_PROCESSED_PCNT" xml:space="preserve">
<data name="ANALYZE_FILES_PROCESSED_PCNT" xml:space="preserve">
<value>{0}% source files processed</value>
</data>
<data name="ANALYZE_FILE_TYPE_OPEN" xml:space="preserve">
<data name="ANALYZE_FILE_TYPE_OPEN" xml:space="preserve">
<value>Unable to determine file type. File open error for {0}</value>
</data>
<data name="ANALYZE_HTML_EXTENSION" xml:space="preserve">
<data name="ANALYZE_HTML_EXTENSION" xml:space="preserve">
<value>The output file does not have an html or htm extension and may not open properly in your browser</value>
</data>
<data name="ANALYZE_LANGUAGE_NOTFOUND" xml:space="preserve">
<data name="ANALYZE_LANGUAGE_NOTFOUND" xml:space="preserve">
<value>File skipped: Language not found for file {0}</value>
</data>
<data name="ANALYZE_NODUPLICATES_HTML_FORMAT" xml:space="preserve">
<data name="ANALYZE_NODUPLICATES_HTML_FORMAT" xml:space="preserve">
<value>Allow duplicate matches argument not supported for html output format. Select a different output format (text/json) or remove the argument.</value>
</data>
<data name="ANALYZE_NOPATTERNS" xml:space="preserve">
<data name="ANALYZE_NOPATTERNS" xml:space="preserve">
<value>No pattern matches were detected for files in source path</value>
</data>
<data name="ANALYZE_PROCESSING_TIMED_OUT" xml:space="preserve">
<value>Overall processing timeout hit.</value>
</data>
<data name="ANALYZE_NOSUPPORTED_FILETYPES" xml:space="preserve">
<data name="ANALYZE_NOSUPPORTED_FILETYPES" xml:space="preserve">
<value>No file types found in source path that are supported</value>
</data>
<data name="ANALYZE_REPORTSIZE_WARN" xml:space="preserve">
<data name="ANALYZE_REPORTSIZE_WARN" xml:space="preserve">
<value>The output.html file size is large and may render slowly. Consider running using alternate output format options as needed.</value>
</data>
<data name="ANALYZE_SIMPLETAGS_HTML_FORMAT" xml:space="preserve">
<data name="ANALYZE_SIMPLETAGS_HTML_FORMAT" xml:space="preserve">
<value>Simple tags only argument not supported for html output format. Select a different output format or remove the argument</value>
</data>
<data name="ANALYZE_UNCOMPRESSED_FILETYPE" xml:space="preserve">
<data name="ANALYZE_UNCOMPRESSED_FILETYPE" xml:space="preserve">
<value>uncompressed</value>
</data>
<data name="ANALYZE_UNSUPPORTED_COMPR_TYPE" xml:space="preserve">
<data name="ANALYZE_UNSUPPORTED_COMPR_TYPE" xml:space="preserve">
<value>unsupported</value>
</data>
<data name="BROWSER_ENVIRONMENT_VAR" xml:space="preserve">
<data name="BROWSER_ENVIRONMENT_VAR" xml:space="preserve">
<value>Unable to launch output.html automatically. Set the BROWSER environment variable to your desired browser and try again or launch your browser and navigate to the file to view the report file manually.</value>
</data>
<data name="BROWSER_START_FAIL" xml:space="preserve">
<data name="BROWSER_START_FAIL" xml:space="preserve">
<value>Unable to launch output.html in default browser. Launch your browser manually to view output.html report file</value>
</data>
<data name="BROWSER_START_SUCCESS" xml:space="preserve">
<data name="BROWSER_START_SUCCESS" xml:space="preserve">
<value>Opening default browser to output.html report</value>
</data>
<data name="CMD_COMPLETED" xml:space="preserve">
<data name="CMD_COMPLETED" xml:space="preserve">
<value>{0} command completed</value>
</data>
<data name="CMD_CRITICAL_FILE_ERR" xml:space="preserve">
<data name="CMD_CRITICAL_FILE_ERR" xml:space="preserve">
<value>Critical error processing file {0}</value>
</data>
<data name="CMD_INVALID_ARG_VALUE" xml:space="preserve">
<data name="CMD_INVALID_ARG_VALUE" xml:space="preserve">
<value>Invalid {0} argument value</value>
</data>
<data name="CMD_INVALID_FILE_OR_DIR" xml:space="preserve">
<data name="CMD_INVALID_FILE_OR_DIR" xml:space="preserve">
<value>Invalid file or directory {0}</value>
</data>
<data name="CMD_INVALID_LOG_PATH" xml:space="preserve">
<data name="CMD_INVALID_LOG_PATH" xml:space="preserve">
<value>The requested log file {0} cannot be written.</value>
</data>
<data name="CMD_INVALID_RULE_PATH" xml:space="preserve">
<data name="CMD_INVALID_RULE_PATH" xml:space="preserve">
<value>Invalid rule path {0}</value>
</data>
<data name="CMD_NORULES_SPECIFIED" xml:space="preserve">
<data name="CMD_NORULES_SPECIFIED" xml:space="preserve">
<value>No rules specified. At least one valid rules path required</value>
</data>
<data name="CMD_NO_OUTPUT" xml:space="preserve">
<data name="CMD_NO_OUTPUT" xml:space="preserve">
<value>No output specified. Raise the console versosity or provide an output file argument.</value>
</data>
<data name="CMD_PREPARING_REPORT" xml:space="preserve">
<data name="CMD_PREPARING_REPORT" xml:space="preserve">
<value>Preparing report</value>
</data>
<data name="CMD_REMINDER_CHECK_LOG" xml:space="preserve">
<data name="CMD_REMINDER_CHECK_LOG" xml:space="preserve">
<value>Additional details may be found in log file at {0}</value>
</data>
<data name="CMD_REPORT_DONE" xml:space="preserve">
<data name="CMD_REPORT_DONE" xml:space="preserve">
<value>{0} report completed</value>
</data>
<data name="CMD_REQUIRED_ARG_MISSING" xml:space="preserve">
<data name="CMD_REQUIRED_ARG_MISSING" xml:space="preserve">
<value>Required {0} argument missing</value>
</data>
<data name="CMD_RUNNING" xml:space="preserve">
<data name="CMD_RUNNING" xml:space="preserve">
<value>{0} command running</value>
</data>
<data name="CMD_VIEW_OUTPUT_FILE" xml:space="preserve">
<data name="CMD_VIEW_OUTPUT_FILE" xml:space="preserve">
<value>See output file at {0}</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="comments" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\comments.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="languages" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\languages.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="PACK_MISSING_OUTPUT_ARG" xml:space="preserve">
<assembly alias="System.Windows.Forms"
name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<data name="comments" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\comments.json;System.Byte[], mscorlib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
</value>
</data>
<data name="languages" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\AppInspector.RulesEngine\Resources\languages.json;System.Byte[], mscorlib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089
</value>
</data>
<data name="PACK_MISSING_OUTPUT_ARG" xml:space="preserve">
<value>Output file not specified for custom rules pack command</value>
</data>
<data name="PACK_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<data name="PACK_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<value>Pack default rules request from outside CLI call not supported. Use custom rules option.</value>
</data>
<data name="PACK_RULES_NO_DEFAULT" xml:space="preserve">
<data name="PACK_RULES_NO_DEFAULT" xml:space="preserve">
<value>Unable to locate local default rules folder. This command is intended to be run as part of the source code build operation only. Use the custom rules option instead.</value>
</data>
<data name="RUNTIME_ERROR_NAMED" xml:space="preserve">
<data name="RUNTIME_ERROR_NAMED" xml:space="preserve">
<value>Additional details may be found in log file at {0}</value>
</data>
<data name="RUNTIME_ERROR_PRELOG" xml:space="preserve">
<data name="RUNTIME_ERROR_PRELOG" xml:space="preserve">
<value>No additional information or log available</value>
</data>
<data name="RUNTIME_ERROR_UNNAMED" xml:space="preserve">
<data name="RUNTIME_ERROR_UNNAMED" xml:space="preserve">
<value>Additional details may be found in log file at {0}</value>
</data>
<data name="TAGDIFF_NO_TAGS_FOUND" xml:space="preserve">
<data name="TAGDIFF_NO_TAGS_FOUND" xml:space="preserve">
<value>No tags found in one or both source paths</value>
</data>
<data name="TAGDIFF_RESULTS_DIFFER" xml:space="preserve">
<data name="TAGDIFF_RESULTS_DIFFER" xml:space="preserve">
<value>Files contain tag differences: </value>
</data>
<data name="TAGDIFF_RESULTS_FAIL" xml:space="preserve">
<data name="TAGDIFF_RESULTS_FAIL" xml:space="preserve">
<value>failed</value>
</data>
<data name="TAGDIFF_RESULTS_GAP" xml:space="preserve">
<data name="TAGDIFF_RESULTS_GAP" xml:space="preserve">
<value>Tags in {0} not detected in {1}:</value>
</data>
<data name="TAGDIFF_RESULTS_SUCCESS" xml:space="preserve">
<data name="TAGDIFF_RESULTS_SUCCESS" xml:space="preserve">
<value>succeeded</value>
</data>
<data name="TAGDIFF_RESULTS_TEST_TYPE" xml:space="preserve">
<data name="TAGDIFF_RESULTS_TEST_TYPE" xml:space="preserve">
<value>Test for all [{0}] in source</value>
</data>
<data name="TAGDIFF_SAME_FILE_ARG" xml:space="preserve">
<data name="TAGDIFF_SAME_FILE_ARG" xml:space="preserve">
<value>Same file passed in for both sources. Test terminated</value>
</data>
<data name="TAGTEST_RESULTS_FAIL" xml:space="preserve">
<data name="TAGTEST_RESULTS_FAIL" xml:space="preserve">
<value>failed</value>
</data>
<data name="TAGTEST_RESULTS_NONE" xml:space="preserve">
<data name="TAGTEST_RESULTS_NONE" xml:space="preserve">
<value>none</value>
</data>
<data name="TAGTEST_RESULTS_SUCCESS" xml:space="preserve">
<data name="TAGTEST_RESULTS_SUCCESS" xml:space="preserve">
<value>succeeded</value>
</data>
<data name="TAGTEST_RESULTS_TAGS_FOUND" xml:space="preserve">
<data name="TAGTEST_RESULTS_TAGS_FOUND" xml:space="preserve">
<value>Found {0} in source</value>
</data>
<data name="TAGTEST_RESULTS_TAGS_MISSING" xml:space="preserve">
<data name="TAGTEST_RESULTS_TAGS_MISSING" xml:space="preserve">
<value>Missing {0} in source</value>
</data>
<data name="TAGTEST_RESULTS_TEST_TYPE" xml:space="preserve">
<data name="TAGTEST_RESULTS_TEST_TYPE" xml:space="preserve">
<value>Tagtest for [{0}]: </value>
</data>
<data name="VERIFY_RULES_DUPLICATEID_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_DUPLICATEID_FAIL" xml:space="preserve">
<value>Rule {0} failed from dupicate rule id specified</value>
</data>
<data name="VERIFY_RULES_LANGUAGE_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_LANGUAGE_FAIL" xml:space="preserve">
<value>Rule {0} failed from unrecognized language specified</value>
</data>
<data name="VERIFY_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<data name="VERIFY_RULES_NO_CLI_DEFAULT" xml:space="preserve">
<value>Verify default rules request from outside CLI call not supported. Use custom rules option.</value>
</data>
<data name="VERIFY_RULES_NULLID_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_NULLID_FAIL" xml:space="preserve">
<value>Rule {0} failed from missing rule id</value>
</data>
<data name="VERIFY_RULES_REGEX_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_REGEX_FAIL" xml:space="preserve">
<value>Rule {0} failed from invalid regex '{1}' with {2}</value>
</data>
<data name="VERIFY_RULES_RESULTS_FAIL" xml:space="preserve">
<data name="VERIFY_RULES_RESULTS_FAIL" xml:space="preserve">
<value>Verify rules failed. See log file for details</value>
</data>
<data name="VERIFY_RULES_RESULTS_SUCCESS" xml:space="preserve">
<data name="VERIFY_RULES_RESULTS_SUCCESS" xml:space="preserve">
<value>Verify rules succeeded</value>
</data>
<data name="VERIFY_RULE_LOADFILE_FAILED" xml:space="preserve">
<data name="VERIFY_RULE_LOADFILE_FAILED" xml:space="preserve">
<value>Rule parsing failed for file {0}</value>
</data>
</root>

Просмотреть файл

@ -1,16 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.ApplicationInspector.Commands
{
using Newtonsoft.Json;
using Newtonsoft.Json;
/// <summary>
/// base for all command operation results
/// </summary>
public class Result
{
[JsonProperty(Order = 1, PropertyName = "appVersion")]
public string? AppVersion { get; set; }
}
namespace Microsoft.ApplicationInspector.Commands;
/// <summary>
/// base for all command operation results
/// </summary>
public class Result
{
[JsonProperty(Order = 1, PropertyName = "appVersion")]
public string? AppVersion { get; set; }
}

Просмотреть файл

@ -1,35 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.ApplicationInspector.Commands
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
namespace Microsoft.ApplicationInspector.Commands;
//Miscellenous common methods needed from several places throughout
public static class RuleSetUtils
{
using Microsoft.ApplicationInspector.RulesEngine;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Linq;
using System.Reflection;
//Miscellenous common methods needed from several places throughout
public static class RuleSetUtils
/// <summary>
/// Common method of retrieving rules from AppInspector.Commands manifest
/// </summary>
/// <param name="loggerFactory">If you want log message, provide a loggerfactory configured to your preferences.</param>
/// <returns>The default RuleSet embedded in the App Inspector binary.</returns>
public static RuleSet GetDefaultRuleSet(ILoggerFactory? loggerFactory = null)
{
/// <summary>
/// Common method of retrieving rules from AppInspector.Commands manifest
/// </summary>
/// <param name="loggerFactory">If you want log message, provide a loggerfactory configured to your preferences.</param>
/// <returns>The default RuleSet embedded in the App Inspector binary.</returns>
public static RuleSet GetDefaultRuleSet(ILoggerFactory? loggerFactory = null)
RuleSet ruleSet = new(loggerFactory);
var assembly = Assembly.GetExecutingAssembly();
var resNames = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (var resName in resNames.Where(x =>
x.StartsWith("Microsoft.ApplicationInspector.Commands.rules.default")))
{
RuleSet ruleSet = new(loggerFactory);
Assembly assembly = Assembly.GetExecutingAssembly();
string[] resNames = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (string resName in resNames.Where(x => x.StartsWith("Microsoft.ApplicationInspector.Commands.rules.default")))
{
Stream? resource = assembly.GetManifestResourceStream(resName);
using StreamReader file = new(resource ?? new MemoryStream());
ruleSet.AddString(file.ReadToEnd(), resName, null);
}
return ruleSet;
var resource = assembly.GetManifestResourceStream(resName);
using StreamReader file = new(resource ?? new MemoryStream());
ruleSet.AddString(file.ReadToEnd(), resName);
}
return ruleSet;
}
}

Просмотреть файл

@ -1,10 +1,11 @@
# Application Inspector Rules
This repository contains the default rule set for [Application Inspector](https://github.com/Microsoft/ApplicationInspector/).
This repository contains the default rule set
for [Application Inspector](https://github.com/Microsoft/ApplicationInspector/).
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
@ -18,8 +19,10 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general)
.
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft
sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.

Просмотреть файл

@ -2,33 +2,45 @@
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
Microsoft takes the security of our software products and services seriously, which includes all source code
repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft)
, [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet)
, [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
If you believe you have found a security vulnerability in any Microsoft-owned repository that
meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10))
, please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
Instead, please report them to the Microsoft Security Response Center (MSRC)
at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If
possible, encrypt your message with our PGP key; please download it from
the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we
received your original message. Additional information can be found
at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
Please include the requested information listed below (as much as you can provide) to help us better understand the
nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit
our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages

Просмотреть файл

@ -3,23 +3,24 @@
**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
- **No CSS support:** Fill out this template with information about how to file issues and get help.
- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps.
- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work
with/help you to determine next steps.
- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide.
*Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
# Support
## How to file issues and get help
## How to file issues and get help
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
feature request as a new Issue.
For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
## Microsoft Support Policy
## Microsoft Support Policy
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.

Просмотреть файл

@ -3,15 +3,21 @@
"name": "Miscellaneous: Advertising Network (Google Adsense)",
"id": "AI000100",
"description": "Miscellaneous: Advertising Network (Google Adsense)",
"tags":[ "CloudServices.AdvertisingNetwork.Google.Adsense" ],
"tags": [
"CloudServices.AdvertisingNetwork.Google.Adsense"
],
"severity": "moderate",
"patterns": [
{
"confidence": "high",
"pattern": "adsense|googleadservices\\.com|googlesyndication\\.com",
"type": "regexword",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -19,15 +25,21 @@
"name": "Miscellaneous: Advertising Network (Outbrain)",
"id": "AI000200",
"description": "Miscellaneous: Advertising Network (Outbrain)",
"tags":[ "CloudServices.AdvertisingNetwork.Outbrain" ],
"tags": [
"CloudServices.AdvertisingNetwork.Outbrain"
],
"severity": "moderate",
"patterns": [
{
"confidence": "high",
"pattern": "outbrain.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -35,15 +47,21 @@
"name": "Miscellaneous: Advertising Network (Bing Ads)",
"id": "AI000300",
"description": "Miscellaneous: Advertising Network (Bing Ads)",
"tags":[ "CloudServices.AdvertisingNetwork.Microsoft.BingAds" ],
"tags": [
"CloudServices.AdvertisingNetwork.Microsoft.BingAds"
],
"severity": "moderate",
"patterns": [
{
"confidence": "high",
"pattern": "bingads.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
}

Просмотреть файл

@ -12,8 +12,12 @@
{
"pattern": ".cloud.databricks.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]

Просмотреть файл

@ -3,14 +3,20 @@
"name": "Cloud Service: Hosting (Amazon Web Services)",
"id": "AI000500",
"description": "Cloud Service: Hosting (Amazon Web Services)",
"tags":[ "CloudServices.Hosting.Amazon.AWS" ],
"tags": [
"CloudServices.Hosting.Amazon.AWS"
],
"severity": "moderate",
"patterns": [
{
"pattern": "\\.amazonaws\\.com|aws\\.amazon\\.com",
"type": "regexword",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -19,20 +25,28 @@
"name": "Cloud Service: Hosting (Apple iCloud)",
"id": "AI000600",
"description": "Cloud Service: Hosting (Apple iCloud)",
"tags":[ "CloudServices.Hosting.Apple.iCloud" ],
"tags": [
"CloudServices.Hosting.Apple.iCloud"
],
"severity": "moderate",
"patterns": [
{
"pattern": "icloud",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
},
{
"pattern": "NSUbiquitousKeyValueStore|URLForUbiquityContainerIdentifier",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -41,14 +55,20 @@
"name": "Cloud Service: Hosting (Microsoft Azure)",
"id": "AI000700",
"description": "Cloud Service: Hosting (Microsoft Azure)",
"tags":[ "CloudServices.Hosting.Microsoft.Azure" ],
"tags": [
"CloudServices.Hosting.Microsoft.Azure"
],
"severity": "moderate",
"patterns": [
{
"pattern": "azure",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -57,19 +77,25 @@
"name": "Cloud Service: Application (Microsoft Office 365)",
"id": "AI000800",
"description": "Cloud Service: Application (Microsoft Office 365)",
"tags":[ "CloudServices.Application.Microsoft.O365" ],
"tags": [
"CloudServices.Application.Microsoft.O365"
],
"severity": "moderate",
"patterns": [
{
"pattern": "o365|m365|office365|office\\.com",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
},
{
"pattern": "graph\\.microsoft\\.com|MicrosoftGraph",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -78,14 +104,20 @@
"name": "Cloud Service: Application (Telemetry)",
"id": "AI000810",
"description": "Cloud Service: Application (Telemetry)",
"applies_to": [ "csharp" ],
"tags":[ "CloudServices.Application.Microsoft.Telemetry" ],
"applies_to": [
"csharp"
],
"tags": [
"CloudServices.Application.Microsoft.Telemetry"
],
"severity": "moderate",
"patterns": [
{
"pattern": "TelemetryClient|Microsoft\\.ApplicationInsights|MetricTelemetry",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -94,14 +126,21 @@
"name": "Cloud Service: Application (Telemetry)",
"id": "AI000820",
"description": "Cloud Service: Application (Telemetry)",
"applies_to": [ "javascript", "typescript" ],
"tags":[ "CloudServices.Application.Microsoft.Telemetry" ],
"applies_to": [
"javascript",
"typescript"
],
"tags": [
"CloudServices.Application.Microsoft.Telemetry"
],
"severity": "moderate",
"patterns": [
{
"pattern": "appInsights.trackMetric",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -110,15 +149,21 @@
"name": "Cloud Service: Application (Telemetry)",
"id": "AI000830",
"description": "Cloud Service: Application (Telemetry)",
"tags":[ "CloudServices.Application.Microsoft.Telemetry" ],
"tags": [
"CloudServices.Application.Microsoft.Telemetry"
],
"severity": "moderate",
"patterns": [
{
"pattern": "applicationinsights",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
}
]
},
@ -126,14 +171,20 @@
"name": "Cloud Service: Hosting (Google Cloud)",
"id": "AI000900",
"description": "Cloud Service: Hosting (Google Cloud)",
"tags":[ "CloudServices.Hosting.Google" ],
"tags": [
"CloudServices.Hosting.Google"
],
"severity": "moderate",
"patterns": [
{
"pattern": "cloud.google.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -142,15 +193,21 @@
"name": "Cloud Service: Hosting (DigitalOcean Cloud)",
"id": "AI001000",
"description": "Cloud Service: Hosting (DigitalOcean Cloud)",
"tags":[ "CloudServices.Hosting.DigitalOcean" ],
"tags": [
"CloudServices.Hosting.DigitalOcean"
],
"severity": "moderate",
"rule_info": "",
"patterns": [
{
"pattern": "digitalocean.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -159,14 +216,20 @@
"name": "Cloud Service: Application (SendGrid)",
"id": "AI001100",
"description": "Cloud Service: Application (SendGrid)",
"tags":[ "CloudServices.Application.SendGrid.Mail" ],
"tags": [
"CloudServices.Application.SendGrid.Mail"
],
"severity": "moderate",
"patterns": [
{
"pattern": "sendgrid.net",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -175,14 +238,21 @@
"name": "Cloud Service: Code Repository (GitHub)",
"id": "AI001200",
"description": "Cloud Service: Code Repository (GitHub)",
"tags":[ "CloudServices.Code.Repo.GitHub" ],
"tags": [
"CloudServices.Code.Repo.GitHub"
],
"severity": "moderate",
"patterns": [
{
"pattern": "github",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -191,14 +261,21 @@
"name": "Cloud Service: Code Repository (GitLab)",
"id": "AI001300",
"description": "Cloud Service: Code Repository (GitLab)",
"tags":[ "CloudServices.Code.Repo.GitLab" ],
"tags": [
"CloudServices.Code.Repo.GitLab"
],
"severity": "moderate",
"patterns": [
{
"pattern": "gitlab",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -207,14 +284,21 @@
"name": "Cloud Service: Code Repository (BitBucket)",
"id": "AI001400",
"description": "Cloud Service: Code Repository (BitBucket)",
"tags":[ "CloudServices.Code.Repo.BitBucket" ],
"tags": [
"CloudServices.Code.Repo.BitBucket"
],
"severity": "moderate",
"patterns": [
{
"pattern": "bitbucket",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -223,14 +307,21 @@
"name": "Cloud Service: Code Repository (SourceForge)",
"id": "AI001500",
"description": "Cloud Service: Code Repository (SourceForge)",
"tags":[ "CloudServices.Code.Repo.SourceForge" ],
"tags": [
"CloudServices.Code.Repo.SourceForge"
],
"severity": "moderate",
"patterns": [
{
"pattern": "sourceforge",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -239,14 +330,21 @@
"name": "Cloud Service: Code Repository (Savannah)",
"id": "AI001600",
"description": "Cloud Service: Code Repository (Savannah)",
"tags":[ "CloudServices.Code.Repo.Savannah" ],
"tags": [
"CloudServices.Code.Repo.Savannah"
],
"severity": "moderate",
"patterns": [
{
"pattern": "savannah\\.(non)?gnu\\.org",
"type": "regex",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -255,14 +353,21 @@
"name": "Cloud Service: Code Package Repository (NuGet)",
"id": "AI001700",
"description": "Cloud Service: Code Package Repository (NuGet)",
"tags":[ "CloudServices.Code.Repo.Microsoft.NuGet" ],
"tags": [
"CloudServices.Code.Repo.Microsoft.NuGet"
],
"severity": "moderate",
"patterns": [
{
"pattern": "nuget",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -271,14 +376,21 @@
"name": "Cloud Service: Code Package Repository (NPM)",
"id": "AI001800",
"description": "Cloud Service: Code Package Repository (NPM)",
"tags":[ "CloudServices.Code.Repo.NPM" ],
"tags": [
"CloudServices.Code.Repo.NPM"
],
"severity": "moderate",
"patterns": [
{
"pattern": "npm",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ]
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
]
}
]
},
@ -286,14 +398,21 @@
"name": "Cloud Service: Code Package Repository (PyPI)",
"id": "AI001900",
"description": "Cloud Service: Code Package Repository (PyPI)",
"tags":[ "CloudServices.Code.Repo.PyPI" ],
"tags": [
"CloudServices.Code.Repo.PyPI"
],
"severity": "moderate",
"patterns": [
{
"pattern": "pypi",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -302,14 +421,21 @@
"name": "Cloud Service: Code Package Repository (LaunchPad)",
"id": "AI002000",
"description": "Cloud Service: Code Package Repository (LaunchPad)",
"tags":[ "CloudServices.Code.Repo.LaunchPad" ],
"tags": [
"CloudServices.Code.Repo.LaunchPad"
],
"severity": "moderate",
"patterns": [
{
"pattern": "launchpad.net",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -318,14 +444,21 @@
"name": "Cloud Service: Code Repository (Visual Studio)",
"id": "AI001410",
"description": "Cloud Service: Code Repository (Visual Studio)",
"tags":[ "CloudServices.Code.Repo.VisualStudio" ],
"tags": [
"CloudServices.Code.Repo.VisualStudio"
],
"severity": "moderate",
"patterns": [
{
"pattern": "visualstudio.com",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -334,15 +467,23 @@
"name": "Cloud Service: CI (TravisCI)",
"id": "AI002100",
"description": "Cloud Service: CI (TravisCI)",
"applies_to":[ "yaml" ],
"tags":[ "CloudServices.Code.CI.TravisCI" ],
"applies_to": [
"yaml"
],
"tags": [
"CloudServices.Code.CI.TravisCI"
],
"severity": "moderate",
"patterns": [
{
"pattern": "(language|dist|matrix):",
"type": "regex",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -351,14 +492,20 @@
"name": "Cloud Service: CI (Circle CI)",
"id": "AI002200",
"description": "Cloud Service: CI (Circle CI)",
"applies_to":[ "yaml" ],
"tags":[ "CloudServices.Code.CI.CircleCI" ],
"applies_to": [
"yaml"
],
"tags": [
"CloudServices.Code.CI.CircleCI"
],
"severity": "moderate",
"patterns": [
{
"pattern": "circleci",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -367,14 +514,20 @@
"name": "Cloud Service: CI (Azure Pipelines)",
"id": "AI002300",
"description": "Cloud Service: CI (Azure Pipelines)",
"applies_to":[ "yaml" ],
"tags":[ "CloudServices.Code.CI.Microsoft.Azure" ],
"applies_to": [
"yaml"
],
"tags": [
"CloudServices.Code.CI.Microsoft.Azure"
],
"severity": "moderate",
"patterns": [
{
"pattern": "steps:",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -383,14 +536,21 @@
"name": "Cloud Service: Hosting App Store (Windows)",
"id": "AI002400",
"description": "Cloud Service: Hosting App Store (Windows)",
"tags":[ "CloudServices.Hosting.Microsoft.AppStore" ],
"tags": [
"CloudServices.Hosting.Microsoft.AppStore"
],
"severity": "moderate",
"patterns": [
{
"pattern": "Windows Store|Windows App Store|WinAppStore",
"type": "regex",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]

Просмотреть файл

@ -3,7 +3,9 @@
"name": "CloudServices: DataStorage (Azure)",
"id": "AI002500",
"description": "CloudServices: DataStorage (Azure Table,File,Blob)",
"applies_to":[ "csharp" ],
"applies_to": [
"csharp"
],
"tags": [
"CloudServices.DataStorage.Microsoft.Azure",
"CloudServices.Hosting.Microsoft.Azure"
@ -13,15 +15,21 @@
{
"pattern": "CloudStorageAccount|Microsoft\\.Azure\\.Storage|Microsoft\\.Azure\\.Cosmos\\.Table|Microsoft\\.Azure\\.Documents|Microsoft\\.WindowsAzure\\.Storage",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
},
{
"pattern": "cosmosdb",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
}
]
},
@ -35,21 +43,29 @@
"yaml",
"pom.xml"
],
"tags":[ "CloudServices.DataStorage.Microsoft.Azure" ],
"tags": [
"CloudServices.DataStorage.Microsoft.Azure"
],
"severity": "moderate",
"patterns": [
{
"pattern": "(BlockBlobService|azure)\\.storage|cosmosdb\\.azure\\.com|BlobStorageAccount",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
},
{
"pattern": "cosmosdb",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
}
]
},
@ -57,20 +73,28 @@
"name": "CloudServices: DataStorage (Azure)",
"id": "AI002700",
"description": "CloudServices: DataStorage (Azure Table,File,Blob)",
"applies_to":[ "javascript" ],
"tags":[ "CloudServices.DataStorage.Microsoft.Azure" ],
"applies_to": [
"javascript"
],
"tags": [
"CloudServices.DataStorage.Microsoft.Azure"
],
"severity": "moderate",
"patterns": [
{
"pattern": "\\.createBlobService",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "medium"
},
{
"pattern": "azure/storage-blob",
"type": "substring",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "medium"
}
]
@ -79,7 +103,9 @@
"name": "CloudServices: DataStorage (AzureKeyVault)",
"id": "AI002710",
"description": "CloudServices: DataStorage (AzureKeyVault)",
"applies_to":[ "csharp" ],
"applies_to": [
"csharp"
],
"tags": [
"CloudServices.DataStorage.Microsoft.AzureKeyVault",
"Data.Sensitive.Secret"
@ -89,22 +115,33 @@
{
"pattern": "new ClientSecret|KeyVaultClient|Microsoft\\.Azure\\.KeyVault|Microsoft\\.Azure\\.Management\\.KeyVault",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
},
{
"pattern": "keyvault",
"type": "regexword",
"scopes": [ "code", "comment" ],
"modifiers": ["i"],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "medium"
},
{
"pattern": "azure\\.keyvault|azure\\.mgmt\\.keyvault",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": ["i"]
"modifiers": [
"i"
]
}
]
},
@ -112,7 +149,9 @@
"name": "CloudServices: DataStorage (HashiCorp Vault)",
"id": "AI002730",
"description": "CloudServices: DataStorage (HashiCorp Vault)",
"applies_to":[ "csharp" ],
"applies_to": [
"csharp"
],
"tags": [
"CloudServices.DataStorage.HashiCorp.Vault",
"Data.Sensitive.Secret"
@ -122,14 +161,21 @@
{
"pattern": "VaultClientSettings|IVaultClient",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
},
{
"pattern": "hashicorp|VaultSharp",
"type": "regex",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -147,8 +193,13 @@
{
"pattern": "vaultSharp|hashicorp|spring vault|import org.springframework.vault",
"type": "regex",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high",
"_comment": "includes vaultsharp here for possible use in PS"
}
@ -162,13 +213,17 @@
"c",
"cpp"
],
"tags":[ "CloudServices.DataStorage.Microsoft.Azure" ],
"tags": [
"CloudServices.DataStorage.Microsoft.Azure"
],
"severity": "moderate",
"patterns": [
{
"pattern": "azure::storage|#include <was\/storage_account\\.h>",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -177,15 +232,21 @@
"name": "CloudServices: DataStorage (Microsoft OneDrive)",
"id": "AI002900",
"description": "CloudServices: DataStorage (Microsoft OneDrive)",
"tags":[ "CloudServices.DataStorage.Microsoft.OneDrive" ],
"tags": [
"CloudServices.DataStorage.Microsoft.OneDrive"
],
"severity": "moderate",
"patterns": [
{
"pattern": "OneDrive",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
}
]
},
@ -197,13 +258,17 @@
"c",
"cpp"
],
"tags":[ "CloudServices.DataStorage.Amazon.S3" ],
"tags": [
"CloudServices.DataStorage.Amazon.S3"
],
"severity": "moderate",
"patterns": [
{
"pattern": "aws/s3/|Aws::S3",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -212,14 +277,20 @@
"name": "CloudServices: DataStorage (Amazon S3)",
"id": "AI003100",
"description": "CloudServices: DataStorage (Amazon S3)",
"applies_to":[ "csharp" ],
"tags":[ "CloudServices.DataStorage.Amazon.S3" ],
"applies_to": [
"csharp"
],
"tags": [
"CloudServices.DataStorage.Amazon.S3"
],
"severity": "moderate",
"patterns": [
{
"pattern": "Amazon.S3",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -228,14 +299,20 @@
"name": "CloudServices: DataStorage (Amazon S3)",
"id": "AI003200",
"description": "CloudServices: DataStorage (Amazon S3)",
"applies_to":[ "python" ],
"tags":[ "CloudServices.DataStorage.Amazon.S3" ],
"applies_to": [
"python"
],
"tags": [
"CloudServices.DataStorage.Amazon.S3"
],
"severity": "moderate",
"patterns": [
{
"pattern": "boto3",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -244,15 +321,21 @@
"name": "CloudServices: DataStorage (Google Drive)",
"id": "AI003300",
"description": "CloudServices: DataStorage (Google Drive)",
"tags":[ "CloudServices.DataStorage.Google.Drive" ],
"tags": [
"CloudServices.DataStorage.Google.Drive"
],
"severity": "moderate",
"patterns": [
{
"pattern": "googleapis.com/upload/drive",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": ["i"]
"modifiers": [
"i"
]
}
]
},
@ -260,15 +343,21 @@
"name": "CloudServices: DataStorage (DropBox)",
"id": "AI003400",
"description": "CloudServices: DataStorage (DropBox)",
"tags":[ "CloudServices.DataStorage.DropBox" ],
"tags": [
"CloudServices.DataStorage.DropBox"
],
"severity": "moderate",
"patterns": [
{
"pattern": "api.dropboxapi.com",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": ["i"]
"modifiers": [
"i"
]
}
]
},
@ -276,15 +365,21 @@
"name": "CloudServices: DataStorage (MediaFire)",
"id": "AI003500",
"description": "CloudServices: DataStorage (MediaFire)",
"tags":[ "CloudServices.DataStorage.MediaFire" ],
"tags": [
"CloudServices.DataStorage.MediaFire"
],
"severity": "moderate",
"patterns": [
{
"pattern": "MediaFire",
"type": "substring",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": ["i"]
"modifiers": [
"i"
]
}
]
}

Просмотреть файл

@ -3,41 +3,68 @@
"name": "CloudServices: Financial (eCommerce)",
"id": "AI003600",
"description": "Data: Financial (eCommerce)",
"tags":[ "CloudServices.Finance.eCommerce" ],
"tags": [
"CloudServices.Finance.eCommerce"
],
"severity": "critical",
"patterns": [
{
"pattern": "wallet|fips-140",
"type": "regexword",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "medium"
},
{
"pattern": "paypal|google pay|ebay|google\\.com/pay/api",
"type": "regex",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
},
{
"pattern": "price|shopping chart|payment",
"type": "regexword",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ]
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
]
},
{
"pattern": "bigcommerce|shopify|bigcartel|woocommerce|wc_api_client|weebly|3dcart|squarespace|connect\\.squareup\\.com",
"type": "regex",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
},
{
"pattern": "demandware|yocart|opencart|magento|magentohost\/api|volusion|x-vtex-api-appkey|alibaba|apple.*pay",
"type": "regexword",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]

Просмотреть файл

@ -3,14 +3,21 @@
"name": "Social Media: Facebook",
"id": "AI003700",
"description": "Social Media Facebook",
"tags":[ "CloudServices.SocialMedia.Facebook" ],
"tags": [
"CloudServices.SocialMedia.Facebook"
],
"severity": "moderate",
"patterns": [
{
"pattern": "facebook",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -19,14 +26,21 @@
"name": "Social Media: Twitter",
"id": "AI003800",
"description": "Social Media (Twitter)",
"tags":[ "CloudServices.SocialMedia.Twitter" ],
"tags": [
"CloudServices.SocialMedia.Twitter"
],
"severity": "moderate",
"patterns": [
{
"pattern": "twitter",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -35,14 +49,21 @@
"name": "Social Media: YouTube",
"id": "AI003900",
"description": "Social Media (YouTube)",
"tags":[ "CloudServices.SocialMedia.YouTube" ],
"tags": [
"CloudServices.SocialMedia.YouTube"
],
"severity": "moderate",
"patterns": [
{
"pattern": "youtube",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -51,14 +72,21 @@
"name": "Social Media: Instagram",
"id": "AI004000",
"description": "Social Media: Instagram",
"tags":[ "CloudServices.SocialMedia.Instagram" ],
"tags": [
"CloudServices.SocialMedia.Instagram"
],
"severity": "moderate",
"patterns": [
{
"pattern": "instagram",
"type": "string",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]
@ -67,14 +95,21 @@
"name": "Social Media: Misc",
"id": "AI004100",
"description": "Social Media: Misc",
"tags":[ "CloudServices.SocialMedia.Misc" ],
"tags": [
"CloudServices.SocialMedia.Misc"
],
"severity": "moderate",
"patterns": [
{
"pattern": "reddit|snapchat|whatsapp|tumblr|qzone|weibo|pinterest|ask\\.fm|flickr|linkedin|odnoklassniki|meetup|discord|diaspora|sociall\\.io|mastodon",
"type": "regexword",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "high"
}
]

Просмотреть файл

@ -3,14 +3,21 @@
"name": "Miscellaneous: Analytics Service",
"id": "AI004200",
"description": "Miscellaneous: Analytics Service",
"tags":[ "CloudServices.Web.Analytics" ],
"tags": [
"CloudServices.Web.Analytics"
],
"severity": "moderate",
"patterns": [
{
"pattern": "analytics|tracker|tracking|tracking cookie|pixel url|tracking script",
"type": "regexword",
"scopes": [ "code", "comment" ],
"modifiers": [ "i" ],
"scopes": [
"code",
"comment"
],
"modifiers": [
"i"
],
"confidence": "low"
}
]
@ -19,14 +26,20 @@
"name": "Miscellaneous: Analytics Service (Facebook)",
"id": "AI004300",
"description": "Miscellaneous: Analytics Service (Facebook)",
"tags":[ "CloudServices.Web.Analytics.Facebook" ],
"tags": [
"CloudServices.Web.Analytics.Facebook"
],
"severity": "moderate",
"patterns": [
{
"pattern": "connect.facebook.net",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -34,14 +47,20 @@
"name": "Miscellaneous: Analytics Service (Google GTag)",
"id": "AI004400",
"description": "Miscellaneous: Analytics Service (Google GTag)",
"tags":[ "CloudServices.Web.Analytics.Google.GTag" ],
"tags": [
"CloudServices.Web.Analytics.Google.GTag"
],
"severity": "moderate",
"patterns": [
{
"pattern": "googletagmanager.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -49,14 +68,20 @@
"name": "Miscellaneous: Analytics Service (Bing)",
"id": "AI004500",
"description": "Miscellaneous: Analytics Service (Bing)",
"tags":[ "CloudServices.Web.Analytics.Microsoft.Bing" ],
"tags": [
"CloudServices.Web.Analytics.Microsoft.Bing"
],
"severity": "moderate",
"patterns": [
{
"pattern": "bat.bing.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -64,14 +89,20 @@
"name": "Miscellaneous: Analytics Service (Twitter)",
"id": "AI004600",
"description": "Miscellaneous: Analytics Service (Twitter)",
"tags":[ "CloudServices.Web.Analytics.Twitter" ],
"tags": [
"CloudServices.Web.Analytics.Twitter"
],
"severity": "moderate",
"patterns": [
{
"pattern": "static.ads-twitter.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -79,14 +110,20 @@
"name": "Miscellaneous: Analytics Service (Outbrain)",
"id": "AI004700",
"description": "Miscellaneous: Analytics Service (Outbrain)",
"tags":[ "CloudServices.Web.Analytics.Outbrain" ],
"tags": [
"CloudServices.Web.Analytics.Outbrain"
],
"severity": "moderate",
"patterns": [
{
"pattern": "amplify.outbrain.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
},
@ -94,14 +131,20 @@
"name": "Miscellaneous: Analytics Service (Pinterest)",
"id": "AI004800",
"description": "Miscellaneous: Analytics Service (Pinterest)",
"tags":[ "CloudServices.Web.Analytics.Pinterest" ],
"tags": [
"CloudServices.Web.Analytics.Pinterest"
],
"severity": "moderate",
"patterns": [
{
"pattern": "s.pinimg.com",
"type": "string",
"scopes": [ "code" ],
"modifiers": [ "i" ]
"scopes": [
"code"
],
"modifiers": [
"i"
]
}
]
}

Просмотреть файл

@ -5,29 +5,46 @@
"description": "Component: Adobe Flash",
"applies_to": [
],
"tags":[ "Component.Executable.Adobe.Flash" ],
"tags": [
"Component.Executable.Adobe.Flash"
],
"severity": "moderate",
"patterns": [
{
"pattern": "\\.(swf|flv)",
"type": "regex",
"scopes": [ "code", "comment" ],
"scopes": [
"code",
"comment"
],
"confidence": "high",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
},
{
"pattern": "adobe flash",
"type": "string",
"scopes": [ "code", "comment" ],
"scopes": [
"code",
"comment"
],
"confidence": "medium",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
},
{
"pattern": "flash",
"type": "regexword",
"scopes": [ "code", "comment" ],
"scopes": [
"code",
"comment"
],
"confidence": "low",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
}
]
},
@ -41,26 +58,36 @@
"csharp",
"vb"
],
"tags":[ "Component.Executable.Microsoft.ActiveX" ],
"tags": [
"Component.Executable.Microsoft.ActiveX"
],
"severity": "moderate",
"patterns": [
{
"pattern": "CoInitialize|CoCreateInstance|comClassInterface|CLSCTX_INPROC_SERVER|ProgId|COleControlModule|ComDefaultInterface|IOleInPlaceObject|IOleControl|IOleObjective",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
},
{
"pattern": "Type\\.GetTypeFromProgID",
"type": "regex",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "medium"
},
{
"pattern": "active-?x",
"type": "regex",
"scopes": [ "code" ],
"modifiers": [ "i" ],
"scopes": [
"code"
],
"modifiers": [
"i"
],
"confidence": "medium"
}
]
@ -69,14 +96,20 @@
"name": "Component: Active-X",
"id": "AI005001",
"description": "Component: Active-X",
"applies_to":[ "vb" ],
"tags":[ "Component.Executable.Microsoft.ActiveX" ],
"applies_to": [
"vb"
],
"tags": [
"Component.Executable.Microsoft.ActiveX"
],
"severity": "moderate",
"patterns": [
{
"pattern": "= CreateObject(",
"type": "substring",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "low"
}
]
@ -85,14 +118,21 @@
"name": "Component: Active-X",
"id": "AI005010",
"description": "Component: Active-X",
"applies_to":[ "javascript", "typescript" ],
"tags":[ "Component.Executable.Microsoft.ActiveX" ],
"applies_to": [
"javascript",
"typescript"
],
"tags": [
"Component.Executable.Microsoft.ActiveX"
],
"severity": "moderate",
"patterns": [
{
"pattern": "new ActiveXObject",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -106,13 +146,17 @@
"cpp",
"csharp"
],
"tags":[ "Component.Executable.Microsoft.COM" ],
"tags": [
"Component.Executable.Microsoft.COM"
],
"severity": "moderate",
"patterns": [
{
"pattern": "CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER|CLSCTX_LOCAL_SERVER|CoCreateInstance",
"type": "regexword",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high"
}
]
@ -126,15 +170,21 @@
"cpp",
"csharp"
],
"tags":[ "Component.Executable.Adobe.PDF" ],
"tags": [
"Component.Executable.Adobe.PDF"
],
"severity": "moderate",
"patterns": [
{
"pattern": ".pdf",
"type": "string",
"scopes": [ "code" ],
"scopes": [
"code"
],
"confidence": "high",
"modifiers": [ "i" ]
"modifiers": [
"i"
]
}
]
},
@ -142,14 +192,20 @@
"name": "Component: Microsoft Silverlight",
"id": "AI005300",
"description": "Component: Microsoft Silverlight",
"tags":[ "Component.Executable.Microsoft.Silverlight" ],
"tags": [
"Component.Executable.Microsoft.Silverlight"
],
"severity": "moderate",
"patterns": [
{
"pattern": "silverlight|\\.xap",
"type": "regex",
"modifiers": [ "i" ],
"scopes": [ "code" ],
"modifiers": [
"i"
],
"scopes": [
"code"
],
"confidence": "high"
}
]

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше