From f65bd19c5c7b30cf5e9e4b80c800bd03b85cefac Mon Sep 17 00:00:00 2001
From: Gabe Stocco <98900+gfs@users.noreply.github.com>
Date: Tue, 30 Aug 2022 15:09:42 -0700
Subject: [PATCH] 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.
---
AppInspector.CLI/AppInspector.CLI.csproj | 142 +-
AppInspector.CLI/CLICmdOptions.cs | 364 ++--
AppInspector.CLI/Program.cs | 756 ++++----
.../PublishProfiles/FolderProfile.pubxml | 22 +-
AppInspector.CLI/ResultsWriter.cs | 164 +-
AppInspector.CLI/TagInfo.cs | 244 ++-
AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs | 1090 +++++------
AppInspector.CLI/Writers/AnalyzeJsonWriter.cs | 79 +-
.../Writers/AnalyzeSarifWriter.cs | 293 ++-
AppInspector.CLI/Writers/AnalyzeTextWriter.cs | 297 ++-
AppInspector.CLI/Writers/CmdResultsWriter.cs | 39 +-
.../Writers/ExportTagsTextWriter.cs | 66 +-
AppInspector.CLI/Writers/JsonWriter.cs | 77 +-
AppInspector.CLI/Writers/TagDiffTextWriter.cs | 71 +-
.../Writers/VerifyRulesTextWriter.cs | 71 +-
AppInspector.CLI/Writers/WriterFactory.cs | 233 ++-
AppInspector.CLI/html/index.html | 114 +-
.../html/resources/css/appinspector.css | 52 +-
.../html/resources/js/appinspector.js | 41 +-
.../AppInspector.Common.csproj | 100 +-
AppInspector.Common/MsgHelp.cs | 195 +-
AppInspector.Common/OpException.cs | 34 +-
.../PublishProfiles/FolderProfile.pubxml | 16 +-
.../Properties/Resources.Designer.cs | 1 -
AppInspector.Common/Properties/Resources.resx | 380 ++--
AppInspector.Common/Utils.cs | 155 +-
.../AppInspector.Logging.csproj | 72 +-
AppInspector.Logging/LogOptions.cs | 70 +-
AppInspector.RulesEngine/AbstractRuleSet.cs | 444 +++--
.../AppInspector.RulesEngine.csproj | 110 +-
AppInspector.RulesEngine/Boundary.cs | 25 +-
AppInspector.RulesEngine/Comment.cs | 29 +-
AppInspector.RulesEngine/Confidence.cs | 18 +-
AppInspector.RulesEngine/LanguageInfo.cs | 40 +-
AppInspector.RulesEngine/Languages.cs | 360 ++--
AppInspector.RulesEngine/Location.cs | 11 +-
AppInspector.RulesEngine/MatchRecord.cs | 306 ++-
.../ApplicationInspectorAnalyzer.cs | 17 +-
.../OatExtensions/ConvertedOatRule.cs | 31 +-
.../OatExtensions/OatRegexWithIndexClause.cs | 30 +-
.../OatRegexWithIndexOperation.cs | 292 ++-
.../OatExtensions/OatSubstringIndexClause.cs | 36 +-
.../OatSubstringIndexOperation.cs | 226 +--
.../OatExtensions/WithinClause.cs | 35 +-
.../OatExtensions/WithinOperation.cs | 335 ++--
AppInspector.RulesEngine/PatternScope.cs | 17 +-
AppInspector.RulesEngine/PatternType.cs | 23 +-
.../Resources/languages.json | 225 ++-
AppInspector.RulesEngine/Rule.cs | 177 +-
AppInspector.RulesEngine/RuleProcessor.cs | 904 +++++----
AppInspector.RulesEngine/RulesVerifier.cs | 423 ++---
.../RulesVerifierOptions.cs | 32 +-
.../RulesVerifierResult.cs | 3 +-
AppInspector.RulesEngine/Ruleset.cs | 26 +-
AppInspector.RulesEngine/SearchCondition.cs | 20 +-
AppInspector.RulesEngine/SearchPattern.cs | 95 +-
AppInspector.RulesEngine/Severity.cs | 63 +-
AppInspector.RulesEngine/TextContainer.cs | 597 +++---
AppInspector.RulesEngine/TypedRuleSet.cs | 91 +-
AppInspector.Tests/AppInspector.Tests.csproj | 58 +-
AppInspector.Tests/Commands/TestAnalyzeCmd.cs | 1555 ++++++++--------
.../Commands/TestExportTagsCmd.cs | 117 +-
.../Commands/TestPackRulesCmd.cs | 61 +-
AppInspector.Tests/Commands/TestTagDiffCmd.cs | 220 +--
.../Commands/TestVerifyRulesCmd.cs | 816 ++++----
.../DefaultRules/TestDefaultRules.cs | 123 +-
.../Languages/LanguagesTests.cs | 144 +-
.../RuleProcessor/LoadRulesTests.cs | 102 +-
.../RuleProcessor/RegexWithIndexTests.cs | 375 ++--
AppInspector.Tests/RuleProcessor/RuleTests.cs | 72 +-
.../RuleProcessor/SubstringWithIndexTests.cs | 398 ++--
.../RuleProcessor/WithinClauseTests.cs | 1040 +++++------
.../RuleProcessor/XmlAndJsonTests.cs | 270 +++
AppInspector.Tests/TestHelpers.cs | 209 ++-
AppInspector/AppInspector.Commands.csproj | 164 +-
AppInspector/Commands/AnalyzeCommand.cs | 1654 ++++++++---------
AppInspector/Commands/ExportTagsCommand.cs | 197 +-
AppInspector/Commands/PackRulesCommand.cs | 187 +-
AppInspector/Commands/TagDiffCommand.cs | 429 +++--
AppInspector/Commands/VerifyRulesCommand.cs | 225 ++-
AppInspector/FileRecord.cs | 53 +-
AppInspector/MetaData.cs | 442 ++---
AppInspector/MetaDataHelper.cs | 621 +++----
AppInspector/MetricTagCounter.cs | 36 +-
.../PublishProfiles/FolderProfile.pubxml | 16 +-
AppInspector/Properties/Resources.Designer.cs | 1 -
AppInspector/Properties/Resources.resx | 378 ++--
AppInspector/Result.cs | 21 +-
AppInspector/RuleSetUtils.cs | 52 +-
AppInspector/rules/README.md | 15 +-
AppInspector/rules/SECURITY.md | 40 +-
AppInspector/rules/SUPPORT.md | 13 +-
.../default/cloud_services/ad_networks.json | 36 +-
.../rules/default/cloud_services/bigdata.json | 8 +-
.../default/cloud_services/cloud_hosting.json | 308 ++-
.../default/cloud_services/data_storage.json | 187 +-
.../default/cloud_services/ecommerce.json | 49 +-
.../default/cloud_services/socialmedia.json | 65 +-
.../default/cloud_services/web_analytics.json | 85 +-
.../default/components/active_content.json | 108 +-
.../rules/default/components/load_dll.json | 50 +-
.../algorithm_implementation.json | 32 +-
.../default/cryptography/certificate.json | 127 +-
.../rules/default/cryptography/ciphers.json | 121 +-
.../default/cryptography/crypto_currency.json | 28 +-
.../rules/default/cryptography/encoding.json | 12 +-
.../rules/default/cryptography/extended.json | 140 +-
.../cryptography/external_libraries.json | 88 +-
.../default/cryptography/hash_algorithm.json | 75 +-
.../default/cryptography/key_derivation.json | 40 +-
.../rules/default/cryptography/protocol.json | 93 +-
.../rules/default/cryptography/random.json | 65 +-
.../rules/default/cryptography/weakssl.json | 132 +-
.../data_handling/compressed_files.json | 12 +-
.../rules/default/data_handling/database.json | 311 +++-
.../data_handling/deserialization.json | 212 ++-
.../default/data_handling/json_parsing.json | 33 +-
.../default/data_handling/media_parsing.json | 32 +-
.../rules/default/data_handling/pastebin.json | 13 +-
.../default/data_handling/xml_parsing.json | 118 +-
.../rules/default/data_types/financial.json | 81 +-
.../rules/default/data_types/media.json | 44 +-
.../rules/default/data_types/secrets.json | 83 +-
.../rules/default/data_types/sensitive.json | 145 +-
.../device_permissions/IOSPermissions.json | 224 ++-
.../rules/default/device_permissions/UWP.json | 208 ++-
.../device_permissions/android_intents.json | 276 ++-
.../rules/default/frameworks/PHP.json | 180 +-
.../rules/default/frameworks/build.json | 184 +-
AppInspector/rules/default/frameworks/c.json | 40 +-
.../default/frameworks/csharp-nonMS.json | 16 +-
.../rules/default/frameworks/java.json | 272 ++-
.../rules/default/frameworks/javascript.json | 287 ++-
.../rules/default/frameworks/logging.json | 92 +-
.../rules/default/frameworks/microsoft.json | 166 +-
.../rules/default/frameworks/python.json | 256 ++-
.../rules/default/frameworks/ruby.json | 48 +-
.../rules/default/frameworks/rust.json | 60 +-
.../rules/default/general/OSS_license.json | 117 +-
.../rules/default/general/code_metrics.json | 99 +-
.../rules/default/general/dependencies.json | 150 +-
.../rules/default/general/hygiene.json | 36 +-
.../rules/default/general/platforms.json | 236 ++-
.../rules/default/general/solutioninfo.json | 292 ++-
.../hashicorp_packers_tmpl.json | 12 +-
.../hashicorp_terraform_tmpl.json | 16 +-
.../infrastructure/microsoft_arm_tmpl.json | 136 +-
.../networkcomms/outbound_network.json | 287 ++-
AppInspector/rules/default/os/acl.json | 152 +-
.../rules/default/os/dynamic_execution.json | 273 ++-
AppInspector/rules/default/os/file_io.json | 327 +++-
AppInspector/rules/default/os/process.json | 120 +-
AppInspector/rules/default/os/setenv.json | 176 +-
.../rules/default/os/system_registry.json | 144 +-
AppInspector/rules/default/os/user_accts.json | 24 +-
.../security_feature/authentication.json | 311 +++-
.../security_feature/authorization.json | 96 +-
.../default/test_frameworks/cpp_testing.json | 122 +-
.../default/test_frameworks/go_testing.json | 16 +-
.../default/test_frameworks/java_testing.json | 716 +++++--
.../test_frameworks/javascript_testing.json | 759 ++++++--
.../test_frameworks/objectiveC_testing.json | 146 +-
.../test_frameworks/powershell_testing.json | 17 +-
.../test_frameworks/python_testing.json | 88 +-
.../default/test_frameworks/ruby_testing.json | 118 +-
.../rules/default/tools/pipeline.json | 144 +-
AppInspector/rules/default/webapp/comms.json | 36 +-
.../rules/default/webapp/headers.json | 132 +-
AppInspector/rules/default/webapp/media.json | 16 +-
.../rules/default/webapp/sessions.json | 156 +-
.../rules/default/webapp/storage.json | 72 +-
Benchmarks/AnalyzeBenchmark.cs | 102 +-
Benchmarks/Benchmarks.csproj | 40 +-
Benchmarks/DistinctBenchmarks.cs | 62 +-
Benchmarks/Program.cs | 23 +-
Directory.Build.props | 12 +-
README.md | 100 +-
version.json | 2 +-
178 files changed, 18443 insertions(+), 13065 deletions(-)
create mode 100644 AppInspector.Tests/RuleProcessor/XmlAndJsonTests.cs
diff --git a/AppInspector.CLI/AppInspector.CLI.csproj b/AppInspector.CLI/AppInspector.CLI.csproj
index 9c30654..5b03395 100644
--- a/AppInspector.CLI/AppInspector.CLI.csproj
+++ b/AppInspector.CLI/AppInspector.CLI.csproj
@@ -1,78 +1,78 @@
-
- Exe
- net6.0
- Microsoft.ApplicationInspector.CLI
- ApplicationInspector.CLI
- © Microsoft Corporation. All rights reserved.
- Application Inspector
- Microsoft
- Microsoft
- 0.0.0-placeholder
- 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.
- 0.0.0.0
- 0.0.0.0
- false
- true
- Microsoft.CST.ApplicationInspector.CLI
- 0.0.0
- https://github.com/microsoft/ApplicationInspector
- Security Static Analyzer
- appinspector
- LICENSE.txt
- icon-128.png
- true
- snupkg
- enable
- 10.0
-
+
+ Exe
+ net6.0
+ Microsoft.ApplicationInspector.CLI
+ ApplicationInspector.CLI
+ © Microsoft Corporation. All rights reserved.
+ Application Inspector
+ Microsoft
+ Microsoft
+ 0.0.0-placeholder
+ 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.
+ 0.0.0.0
+ 0.0.0.0
+ false
+ true
+ Microsoft.CST.ApplicationInspector.CLI
+ 0.0.0
+ https://github.com/microsoft/ApplicationInspector
+ Security Static Analyzer
+ appinspector
+ LICENSE.txt
+ icon-128.png
+ true
+ snupkg
+ enable
+ 10.0
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AppInspector.CLI/CLICmdOptions.cs b/AppInspector.CLI/CLICmdOptions.cs
index 1c815ee..84a427e 100644
--- a/AppInspector.CLI/CLICmdOptions.cs
+++ b/AppInspector.CLI/CLICmdOptions.cs
@@ -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;
+
+///
+/// CLI command option classes add output arguments to common properties for each command verb
+///
+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; }
- ///
- /// CLI command option classes add output arguments to common properties for each command verb
- ///
- ///
- 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; }
- ///
- /// Return a success error code when no matches were found but operation was apparently successful. Useful for CI scenarios
- ///
- [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 FilePathExclusions { get; set; } = System.Array.Empty();
-
- [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 ,. Default: Medium,High. [High|Medium|Low]", Default = new Confidence[]{ Confidence.High, Confidence.Medium })]
- public IEnumerable ConfidenceFilters { get; set; } = new Confidence[] { Confidence.High, Confidence.Medium };
-
- [Option("severity-filters", Required = false, Separator = ',',
- HelpText =
- "Output only matches with specified severity ,. Default: All are enabled. [Critical|Important|Moderate|BestPractice|ManualReview]", Default = new Severity[] { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview })]
- public IEnumerable SeverityFilters { get; set; } = new Severity[] { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview };
- }
-
- ///
- /// CLI command distinct arguments
- ///
- [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 SourcePath { get; set; } = System.Array.Empty();
-
- [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 SourcePath1 { get; set; } = System.Array.Empty();
-
- [Option("src2", Required = true, HelpText = "Source 2 to compare (commaa separated)")]
- public IEnumerable SourcePath2 { get; set; } = System.Array.Empty();
-
- [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; }
+
+ ///
+ /// Return a success error code when no matches were found but operation was apparently successful. Useful for CI
+ /// scenarios
+ ///
+ [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 FilePathExclusions { get; set; } = Array.Empty();
+
+ [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 ,. Default: Medium,High. [High|Medium|Low]",
+ Default = new[] { Confidence.High, Confidence.Medium })]
+ public IEnumerable ConfidenceFilters { get; set; } = new[] { Confidence.High, Confidence.Medium };
+
+ [Option("severity-filters", Required = false, Separator = ',',
+ HelpText =
+ "Output only matches with specified severity ,. Default: All are enabled. [Critical|Important|Moderate|BestPractice|ManualReview]",
+ Default = new[]
+ { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview })]
+ public IEnumerable SeverityFilters { get; set; } = new[]
+ { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview };
+}
+
+///
+/// CLI command distinct arguments
+///
+[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 SourcePath { get; set; } = Array.Empty();
+
+ [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 SourcePath1 { get; set; } = Array.Empty();
+
+ [Option("src2", Required = true, HelpText = "Source 2 to compare (commaa separated)")]
+ public IEnumerable SourcePath2 { get; set; } = Array.Empty();
+
+ [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; }
+}
\ No newline at end of file
diff --git a/AppInspector.CLI/Program.cs b/AppInspector.CLI/Program.cs
index 706af38..fb66876 100644
--- a/AppInspector.CLI/Program.cs
+++ b/AppInspector.CLI/Program.cs
@@ -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
+ ///
+ /// CLI program entry point which defines command verbs and options to running
+ ///
+ ///
+ public static int Main(string[] args)
{
- private static ILoggerFactory loggerFactory = new LoggerFactory();
+ var finalResult = (int)Utils.ExitCode.CriticalError;
- ///
- /// CLI program entry point which defines command verbs and options to running
- ///
- ///
- 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 { settings.CaseInsensitiveEnumValues = true; })
+ .ParseArguments(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;
+ });
}
-
- ///
- /// 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
- ///
- ///
- 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;
+ }
+
+ ///
+ /// 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
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// Ensure output file path can be written to
+ ///
+ ///
+ private static bool CanWritePath(string filePath)
+ {
+ try
+ {
+ File.WriteAllText(filePath, ""); //verify ability to write to location
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ ///
+ /// This method gets a logging factory with Console disabled when the progress bar is enabled. Does not change the
+ /// original options.
+ ///
+ /// The original options.
+ /// A logging factory with adjusted settings.
+ 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);
}
- ///
- /// Ensure output file path can be written to
- ///
- ///
- 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(),
+ 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);
}
-
-
-
- ///
- /// This method gets a logging factory with Console disabled when the progress bar is enabled. Does not change the original options.
- ///
- /// The original options.
- /// A logging factory with adjusted settings.
- 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(),
- 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;
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Properties/PublishProfiles/FolderProfile.pubxml b/AppInspector.CLI/Properties/PublishProfiles/FolderProfile.pubxml
index 03f13ad..d728993 100644
--- a/AppInspector.CLI/Properties/PublishProfiles/FolderProfile.pubxml
+++ b/AppInspector.CLI/Properties/PublishProfiles/FolderProfile.pubxml
@@ -3,15 +3,15 @@
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
-
- FileSystem
- Release
- Any CPU
- netcoreapp3.1
- bin\Release\netcoreapp3.1\publish\
- win-x86
- false
- False
- False
-
+
+ FileSystem
+ Release
+ Any CPU
+ netcoreapp3.1
+ bin\Release\netcoreapp3.1\publish\
+ win-x86
+ false
+ False
+ False
+
\ No newline at end of file
diff --git a/AppInspector.CLI/ResultsWriter.cs b/AppInspector.CLI/ResultsWriter.cs
index 66ac315..ef78679 100644
--- a/AppInspector.CLI/ResultsWriter.cs
+++ b/AppInspector.CLI/ResultsWriter.cs
@@ -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;
+
+///
+/// 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
+///
+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();
+ _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);
+ }
///
- /// 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
///
- public class ResultsWriter
+ ///
+ ///
+ internal void Finalize(CommandResultsWriter? outputWriter, string commandName)
{
- public ResultsWriter(ILoggerFactory loggerFactory)
- {
- _logger = loggerFactory.CreateLogger();
- _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);
- }
-
- ///
- /// Allow for final actions if even and common file path notice to console
- /// Most Writer.Write operations flushandclose the stream automatically but .Flush
- ///
- ///
- ///
- 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));
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/TagInfo.cs b/AppInspector.CLI/TagInfo.cs
index 91370c6..c17f627 100644
--- a/AppInspector.CLI/TagInfo.cs
+++ b/AppInspector.CLI/TagInfo.cs
@@ -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;
+
+///
+/// Root parent for tag group preferences file and used in Writers\AnalyzeHtmlWriter.cs
+///
+public class TagCategory
{
- using DotLiquid;
- using Newtonsoft.Json;
- using System;
- using System.Collections.Generic;
- using System.Text.RegularExpressions;
-
- ///
- /// Root parent for tag group preferences file and used in Writers\AnalyzeHtmlWriter.cs
- ///
- 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();
+ }
- [JsonProperty(PropertyName = "groups")]
- public List? Groups { get; set; }
+ [JsonProperty(PropertyName = "categoryName")]
+ public string? Name { get; set; }
- public TagCategory()
+ [JsonProperty(PropertyName = "groups")]
+ public List? Groups { get; set; }
+}
+
+///
+/// Used to read customizable preference for Profile page e.g. rules\profile\profile.json
+///
+public class TagGroup : Drop
+{
+ public TagGroup()
+ {
+ Patterns = new List();
+ }
+
+ [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? 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();
+ _searchPattern = value;
+ _expression = null;
}
}
- ///
- /// Used to read customizable preference for Profile page e.g. rules\profile\profile.json
- ///
- 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? Patterns { get; set; }
-
- public TagGroup()
+ get
{
- Patterns = new List();
+ 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";
}
- ///
- /// Primary use is development of lists of tags with specific group or pattern properties in reporting
- ///
- 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; }
+///
+/// Primary use is development of lists of tags with specific group or pattern properties in reporting
+///
+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; }
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs b/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs
index 3d46efb..62d4e76 100644
--- a/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs
+++ b/AppInspector.CLI/Writers/AnalyzeHtmlWriter.cs
@@ -1,665 +1,601 @@
// 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 System.Net;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using DotLiquid;
+using DotLiquid.FileSystems;
+using Microsoft.ApplicationInspector.Commands;
+using Microsoft.ApplicationInspector.Common;
+using Microsoft.ApplicationInspector.RulesEngine;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Newtonsoft.Json;
+
+namespace Microsoft.ApplicationInspector.CLI;
+
+public class AnalyzeHtmlWriter : CommandResultsWriter
{
- using DotLiquid;
- using DotLiquid.FileSystems;
- using Microsoft.ApplicationInspector.Common;
- using Microsoft.ApplicationInspector.Commands;
- using Microsoft.ApplicationInspector.RulesEngine;
- using Newtonsoft.Json;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Text.RegularExpressions;
- using Microsoft.Extensions.Logging.Abstractions;
- using Microsoft.Extensions.Logging;
+ private readonly ILogger _logger;
+ private AnalyzeResult? _analyzeResult;
- public class AnalyzeHtmlWriter : CommandResultsWriter
+ private MetaData? _appMetaData;
+
+ public AnalyzeHtmlWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
- private readonly ILogger _logger;
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance;
+ KeyedTagInfoLists = new Dictionary>();
+ KeyedSortedTagInfoLists = new Dictionary>();
+ }
- public Dictionary> KeyedTagInfoLists { get; } = new Dictionary>();
+ public Dictionary> KeyedTagInfoLists { get; } = new();
- public Dictionary> KeyedSortedTagInfoLists { get; } = new Dictionary>();
+ public Dictionary> KeyedSortedTagInfoLists { get; } = new();
- public List? TagGroupPreferences { get; set; }//read preferred list of groups and tags for profile / features page
+ public List?
+ TagGroupPreferences { get; set; } //read preferred list of groups and tags for profile / features page
- private MetaData? _appMetaData;
- private AnalyzeResult? _analyzeResult;
+ ///
+ /// Pre: AnalyzeCommand GetResults created and populated from RulesEngine
+ ///
+ ///
+ ///
+ ///
+ public override void WriteResults(Result result, CLICommandOptions cLICommandOptions, bool autoClose = true)
+ {
+ //recover metadata results from prior analyzecommand GetResults()
+ _analyzeResult = (AnalyzeResult)result;
+ _appMetaData = _analyzeResult.Metadata;
- public AnalyzeHtmlWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
+ PopulateTagGroups();
+ WriteHtmlResult();
+ }
+
+ private void WriteHtmlResult()
+ {
+ RenderResultsSafeforHTML();
+
+ //Grab any local css and js files that are needed i.e. don't have hosted URL's or are proprietary
+ var allCSS = "");
+
+ RegisterSafeType(typeof(MetaData));
+
+ //Prepare data for use in appinspector.js and html partials resources
+ var htmlTemplate = Template.Parse(htmlTemplateText);
+ var data = new Dictionary();
+ data["MetaData"] = _appMetaData ?? new MetaData("", "");
+
+ var hashData = new Hash();
+ string? jsonData;
+ try
{
- _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance;
- KeyedTagInfoLists = new Dictionary>();
- KeyedSortedTagInfoLists = new Dictionary>();
+ jsonData = JsonConvert.SerializeObject(data);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(
+ "Failed to write HTML report. Failed to serialize JSON representation of results in memory. {Type} : {Message}",
+ e.GetType().Name, e.Message);
+ throw;
}
- ///
- /// Pre: AnalyzeCommand GetResults created and populated from RulesEngine
- ///
- ///
- ///
- ///
- public override void WriteResults(Result result, CLICommandOptions cLICommandOptions, bool autoClose = true)
- {
- //recover metadata results from prior analyzecommand GetResults()
- _analyzeResult = (AnalyzeResult)result;
- _appMetaData = _analyzeResult.Metadata;
+ hashData["json"] = jsonData; //json serialization required for [js] access to objects
+ hashData["application_version"] = Utils.GetVersionString();
- PopulateTagGroups();
- WriteHtmlResult();
- }
+ //add dynamic sets of groups of taginfo read from preferences for Profile page
+ var tagGroupList = GetCategoryTagGroups("profile");
+ hashData["groups"] = tagGroupList;
- private void WriteHtmlResult()
- {
- RenderResultsSafeforHTML();
+ //add summary values for sorted tags lists of taginfo
+ foreach (var outerKey in KeyedSortedTagInfoLists.Keys)
+ hashData.Add(outerKey, KeyedSortedTagInfoLists[outerKey]);
- //Grab any local css and js files that are needed i.e. don't have hosted URL's or are proprietary
- string allCSS = "");
+ private void RegisterSafeType(Type type)
+ {
+ Template.RegisterSafeType(type, t => t.ToString());
+ Template.RegisterSafeType(type, type.GetMembers(BindingFlags.Instance).Select(e => e.Name).ToArray());
+ }
- RegisterSafeType(typeof(MetaData));
+ private string MergeResourceFiles(string inputPath)
+ {
+ StringBuilder stringBuilder = new();
- //Prepare data for use in appinspector.js and html partials resources
- var htmlTemplate = Template.Parse(htmlTemplateText);
- var data = new Dictionary();
- data["MetaData"] = _appMetaData ?? new MetaData("", "");
-
- var hashData = new Hash();
- string? jsonData;
+ if (Directory.Exists(inputPath))
try
{
- jsonData = JsonConvert.SerializeObject(data);
- }
- catch (Exception e)
- {
- _logger.LogError("Failed to write HTML report. Failed to serialize JSON representation of results in memory. {Type} : {Message}", e.GetType().Name, e.Message);
- throw;
- }
- hashData["json"] = jsonData;//json serialization required for [js] access to objects
- hashData["application_version"] = Utils.GetVersionString();
-
- //add dynamic sets of groups of taginfo read from preferences for Profile page
- List tagGroupList = GetCategoryTagGroups("profile");
- hashData["groups"] = tagGroupList;
-
- //add summary values for sorted tags lists of taginfo
- foreach (string outerKey in KeyedSortedTagInfoLists.Keys)
- {
- hashData.Add(outerKey, KeyedSortedTagInfoLists[outerKey]);
- }
-
- hashData["cputargets"] = _appMetaData?.CPUTargets;
- hashData["apptypes"] = _appMetaData?.AppTypes ?? new List();
- hashData["packagetypes"] = _appMetaData?.PackageTypes ?? new List();
- hashData["ostargets"] = _appMetaData?.OSTargets ?? new List();
- hashData["outputs"] = _appMetaData?.Outputs ?? new List();
- hashData["filetypes"] = _appMetaData?.FileExtensions ?? new List();
- hashData["tagcounters"] = ConvertTagCounters(_appMetaData?.TagCounters ?? new List());
-
- //final render and close
- var htmlResult = htmlTemplate.Render(hashData);
- TextWriter?.Write(htmlResult);
- FlushAndClose();
- }
-
- private void RegisterSafeType(Type type)
- {
- Template.RegisterSafeType(type, (t) => t.ToString());
- Template.RegisterSafeType(type, type.GetMembers(BindingFlags.Instance).Select((e) => e.Name).ToArray());
- }
-
- private string MergeResourceFiles(string inputPath)
- {
- StringBuilder stringBuilder = new();
-
- if (Directory.Exists(inputPath))
- {
- try
- {
- IEnumerable srcfileList = Directory.EnumerateFiles(inputPath, "*.*", SearchOption.AllDirectories);
- if (!srcfileList.Any())
- {
- throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_FILE_OR_DIR, inputPath));
- }
-
- foreach (string fileName in srcfileList)
- {
- stringBuilder.Append(string.Format("\n\n/*FILE: {0}*/\n\n", fileName));
- stringBuilder.Append(File.ReadAllText(fileName));
- }
- }
- catch (Exception)
- {
+ var srcfileList = Directory.EnumerateFiles(inputPath, "*.*", SearchOption.AllDirectories);
+ if (!srcfileList.Any())
throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_FILE_OR_DIR, inputPath));
+
+ foreach (var fileName in srcfileList)
+ {
+ stringBuilder.Append(string.Format("\n\n/*FILE: {0}*/\n\n", fileName));
+ stringBuilder.Append(File.ReadAllText(fileName));
}
}
+ catch (Exception)
+ {
+ throw new OpException(MsgHelp.FormatString(MsgHelp.ID.CMD_INVALID_FILE_OR_DIR, inputPath));
+ }
- return stringBuilder.ToString();
- }
+ return stringBuilder.ToString();
+ }
-
- ///
- /// Renders code i.e. user input safe for html display for default report
- /// Delayed html encoding allows original values to be rendered for the output form i.e.
- /// json, text formats, even nuget objects retain the originals for readability and
- /// for managing output transformations as desired
- ///
- private void RenderResultsSafeforHTML()
+
+ ///
+ /// Renders code i.e. user input safe for html display for default report
+ /// Delayed html encoding allows original values to be rendered for the output form i.e.
+ /// json, text formats, even nuget objects retain the originals for readability and
+ /// for managing output transformations as desired
+ ///
+ private void RenderResultsSafeforHTML()
+ {
+ //safeguard simple string meta-data
+ if (_appMetaData?.ApplicationName != null)
+ _appMetaData.ApplicationName = SafeString(_appMetaData?.ApplicationName);
+
+ if (_appMetaData?.Description != null)
+ _appMetaData.Description = SafeString(_appMetaData?.Description);
+
+ if (_appMetaData?.Authors != null)
+ _appMetaData.Authors = SafeString(_appMetaData?.Authors);
+
+ if (_appMetaData?.SourceVersion != null)
+ _appMetaData.SourceVersion = SafeString(_appMetaData?.SourceVersion);
+
+ //safeguard lists data
+ SafeList(_appMetaData?.AppTypes);
+ SafeList(_appMetaData?.CloudTargets);
+ SafeList(_appMetaData?.OSTargets);
+ SafeList(_appMetaData?.Outputs);
+ SafeList(_appMetaData?.PackageTypes);
+ SafeList(_appMetaData?.Targets);
+ SafeList(_appMetaData?.CPUTargets);
+
+ //safeguard displayable fields in match records
+ foreach (var matchRecord in _appMetaData?.Matches ?? new List())
{
- //safeguard simple string meta-data
- if (_appMetaData?.ApplicationName != null)
- _appMetaData.ApplicationName = SafeString(_appMetaData?.ApplicationName);
-
- if (_appMetaData?.Description != null)
- _appMetaData.Description = SafeString(_appMetaData?.Description);
-
- if (_appMetaData?.Authors != null)
- _appMetaData.Authors = SafeString(_appMetaData?.Authors);
-
- if (_appMetaData?.SourceVersion != null)
- _appMetaData.SourceVersion = SafeString(_appMetaData?.SourceVersion);
-
- //safeguard lists data
- SafeList(_appMetaData?.AppTypes);
- SafeList(_appMetaData?.CloudTargets);
- SafeList(_appMetaData?.OSTargets);
- SafeList(_appMetaData?.Outputs);
- SafeList(_appMetaData?.PackageTypes);
- SafeList(_appMetaData?.Targets);
- SafeList(_appMetaData?.CPUTargets);
-
- //safeguard displayable fields in match records
- foreach (MatchRecord matchRecord in _appMetaData?.Matches ?? new List())
- {
- //safeguard sample output now that we've matched properties for blocking browser xss
- matchRecord.Sample = System.Net.WebUtility.HtmlEncode(matchRecord.Sample);
- matchRecord.Excerpt = System.Net.WebUtility.HtmlEncode(matchRecord.Excerpt);
- }
+ //safeguard sample output now that we've matched properties for blocking browser xss
+ matchRecord.Sample = WebUtility.HtmlEncode(matchRecord.Sample);
+ matchRecord.Excerpt = WebUtility.HtmlEncode(matchRecord.Excerpt);
}
+ }
- private void SafeList(List? valuesList)
+ private void SafeList(List? valuesList)
+ {
+ for (var i = 0; i < valuesList?.Count; i++) valuesList[i] = SafeString(valuesList[i]);
+ }
+
+ private string SafeString(string? value)
+ {
+ if (!string.IsNullOrEmpty(value)) return WebUtility.HtmlEncode(value);
+
+ return "";
+ }
+
+ ///
+ /// Processing for organizing results into easy to use TagGroups for customizable display organization in HTML UI
+ ///
+ public void PopulateTagGroups()
+ {
+ //read default/user preferences on what tags to report presence on and groupings
+ if (File.Exists(Utils.GetPath(Utils.AppPath.tagGroupPref)))
+ TagGroupPreferences =
+ JsonConvert.DeserializeObject>(
+ File.ReadAllText(Utils.GetPath(Utils.AppPath.tagGroupPref)));
+ else
+ TagGroupPreferences = new List();
+
+ string[] unSupportedGroupsOrPatterns = { "metric", "dependency" };
+
+ //for each preferred group of tag patterns determine if at least one instance was detected
+ foreach (var tagCategory in TagGroupPreferences ?? new List())
+ foreach (var tagGroup in tagCategory.Groups ?? new List())
{
- for (int i = 0; i < valuesList?.Count; i++)
+ if (string.IsNullOrEmpty(tagGroup.Title))
{
- valuesList[i] = SafeString(valuesList[i]);
- }
- }
-
- private string SafeString(string? value)
- {
- if (!string.IsNullOrEmpty(value))
- {
- return System.Net.WebUtility.HtmlEncode(value);
+ _logger.LogWarning("Tag group with no title skipped");
+ continue;
}
- return "";
- }
+ var test = tagGroup.Title.ToLower().Contains(unSupportedGroupsOrPatterns[0]);
+ if (unSupportedGroupsOrPatterns.Any(x => tagGroup.Title.ToLower().Contains(x)))
+ _logger.LogWarning(
+ "Unsupported tag group or pattern detected '{title}'. See online documentation at https://github.com/microsoft/ApplicationInspector/wiki/3.5-Tags",
+ tagGroup.Title);
- ///
- /// Processing for organizing results into easy to use TagGroups for customizable display organization in HTML UI
- ///
- public void PopulateTagGroups()
- {
- //read default/user preferences on what tags to report presence on and groupings
- if (File.Exists(Utils.GetPath(Utils.AppPath.tagGroupPref)))
+ foreach (var pattern in tagGroup.Patterns ?? new List())
{
- TagGroupPreferences = JsonConvert.DeserializeObject>(File.ReadAllText(Utils.GetPath(Utils.AppPath.tagGroupPref)));
- }
- else
- {
- TagGroupPreferences = new List();
- }
+ pattern.Detected = _appMetaData?.UniqueTags is not null &&
+ _appMetaData.UniqueTags.Any(v => v == pattern.SearchPattern);
+ if (unSupportedGroupsOrPatterns.Any(x => pattern.SearchPattern.ToLower().Contains(x)))
+ _logger.LogWarning(
+ "Unsupported tag group or pattern detected '{pattern}'. See online documentation at https://github.com/microsoft/ApplicationInspector/wiki/3.5-Tags",
+ pattern.SearchPattern);
- string[] unSupportedGroupsOrPatterns = new string[] { "metric", "dependency" };
-
- //for each preferred group of tag patterns determine if at least one instance was detected
- foreach (TagCategory tagCategory in TagGroupPreferences ?? new List())
- {
- foreach (TagGroup tagGroup in tagCategory.Groups ?? new List())
+ //create dynamic "category" groups of tags with pattern relationship established from TagReportGroups.json
+ //that can be used to populate reports with various attributes for each tag detected
+ if (pattern.Detected)
{
- if (string.IsNullOrEmpty(tagGroup.Title))
- {
- _logger.LogWarning("Tag group with no title skipped");
- continue;
- }
-
- bool test = tagGroup.Title.ToLower().Contains(unSupportedGroupsOrPatterns[0]);
- if (unSupportedGroupsOrPatterns.Any(x => tagGroup.Title.ToLower().Contains(x)))
- {
- _logger.LogWarning("Unsupported tag group or pattern detected '{title}'. See online documentation at https://github.com/microsoft/ApplicationInspector/wiki/3.5-Tags", tagGroup.Title);
- }
-
- foreach (TagSearchPattern pattern in tagGroup.Patterns ?? new List())
- {
- pattern.Detected = _appMetaData?.UniqueTags is not null && _appMetaData.UniqueTags.Any(v => v == pattern.SearchPattern);
- if (unSupportedGroupsOrPatterns.Any(x => pattern.SearchPattern.ToLower().Contains(x)))
- {
- _logger.LogWarning("Unsupported tag group or pattern detected '{pattern}'. See online documentation at https://github.com/microsoft/ApplicationInspector/wiki/3.5-Tags", pattern.SearchPattern);
- }
-
- //create dynamic "category" groups of tags with pattern relationship established from TagReportGroups.json
- //that can be used to populate reports with various attributes for each tag detected
- if (pattern.Detected)
- {
- bool uniqueTagsOnly = tagCategory.Type == TagCategory.tagInfoType.uniqueTags;
- KeyedTagInfoLists["tagGrp" + tagGroup.DataRef] = GetTagInfoListByTagGroup(tagGroup, uniqueTagsOnly);
- }
- }
+ var uniqueTagsOnly = tagCategory.Type == TagCategory.tagInfoType.uniqueTags;
+ KeyedTagInfoLists["tagGrp" + tagGroup.DataRef] = GetTagInfoListByTagGroup(tagGroup, uniqueTagsOnly);
}
}
-
- //create simple ranked page lists HTML use
- KeyedSortedTagInfoLists["tagGrpAllTagsByConfidence"] = GetTagInfoListByConfidence();
- KeyedSortedTagInfoLists["tagGrpAllTagsBySeverity"] = GetTagInfoListBySeverity();
- KeyedSortedTagInfoLists["tagGrpAllTagsByName"] = GetTagInfoListByName();
}
- ///
- /// Get a list of TagGroup for a given category section name e.g. profile
- ///
- ///
- ///
- public List GetCategoryTagGroups(string category)
- {
- List result = new();
- //get all tag groups for specified category
- foreach (TagCategory categoryTagGroup in TagGroupPreferences ?? new List())
+ //create simple ranked page lists HTML use
+ KeyedSortedTagInfoLists["tagGrpAllTagsByConfidence"] = GetTagInfoListByConfidence();
+ KeyedSortedTagInfoLists["tagGrpAllTagsBySeverity"] = GetTagInfoListBySeverity();
+ KeyedSortedTagInfoLists["tagGrpAllTagsByName"] = GetTagInfoListByName();
+ }
+
+ ///
+ /// Get a list of TagGroup for a given category section name e.g. profile
+ ///
+ ///
+ ///
+ public List GetCategoryTagGroups(string category)
+ {
+ List result = new();
+ //get all tag groups for specified category
+ foreach (var categoryTagGroup in TagGroupPreferences ?? new List())
+ if (categoryTagGroup.Name == category)
{
- if (categoryTagGroup.Name == category)
- {
- result = categoryTagGroup.Groups ?? new List();
- break;
- }
+ result = categoryTagGroup.Groups ?? new List();
+ break;
}
- //now get all matches for that group i.e. Authentication
- foreach (TagGroup group in result)
+ //now get all matches for that group i.e. Authentication
+ foreach (var group in result) GetTagInfoListByTagGroup(group);
+
+ return result;
+ }
+
+ ///
+ /// MetaData.UniqueTags should already exists and be created incrementally but here in case
+ ///
+ ///
+ private HashSet GetUniqueTags()
+ {
+ HashSet results = new();
+
+ foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var tag in match.Tags ?? Array.Empty())
+ results.Add(tag);
+
+ return results;
+ }
+
+ ///
+ /// Builds list of matching tags by profile pattern
+ /// Ensures only one instance of a given tag in results unlike GetAllMatchingTags method
+ /// with highest confidence level for that tag pattern
+ ///
+ ///
+ ///
+ private List GetTagInfoListByTagGroup(TagGroup tagGroup, bool addNotFound = true)
+ {
+ List result = new();
+ HashSet hashSet = new();
+
+ foreach (var pattern in tagGroup.Patterns ?? new List())
+ if (pattern.Detected) //set at program.RollUp already so don't search for again
{
- GetTagInfoListByTagGroup(group);
- }
-
- return result;
- }
-
- ///
- /// MetaData.UniqueTags should already exists and be created incrementally but here in case
- ///
- ///
- private HashSet GetUniqueTags()
- {
- HashSet results = new();
-
- foreach (MatchRecord match in _appMetaData?.Matches ?? new List())
- {
- foreach (string tag in match.Tags ?? Array.Empty())
+ var tagPatternRegex = pattern.Expression;
+ if (_appMetaData?.TotalMatchesCount > 0)
{
- results.Add(tag);
- }
- }
-
- return results;
- }
-
- ///
- /// Builds list of matching tags by profile pattern
- /// Ensures only one instance of a given tag in results unlike GetAllMatchingTags method
- /// with highest confidence level for that tag pattern
- ///
- ///
- ///
- private List GetTagInfoListByTagGroup(TagGroup tagGroup, bool addNotFound = true)
- {
- List result = new();
- HashSet hashSet = new();
-
- foreach (TagSearchPattern pattern in tagGroup.Patterns ?? new List())
- {
- if (pattern.Detected)//set at program.RollUp already so don't search for again
- {
- var tagPatternRegex = pattern.Expression;
- if (_appMetaData?.TotalMatchesCount > 0)
- {
- foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var tagItem in match.Tags ?? Array.Empty())
+ if (tagPatternRegex.IsMatch(tagItem))
{
- foreach (var tagItem in match.Tags ?? Array.Empty())
- {
- if (tagPatternRegex.IsMatch(tagItem))
- {
- if (!hashSet.Contains(pattern.SearchPattern))
- {
- result.Add(new TagInfo
- {
- Tag = tagItem,
- Confidence = match.Confidence.ToString(),
- Severity = match.Severity.ToString(),
- ShortTag = pattern.DisplayName,
- StatusIcon = pattern.DetectedIcon,
- Detected = true
- });
-
- hashSet.Add(pattern.SearchPattern);
-
- pattern.Confidence = match.Confidence.ToString();
- }
- else
- {
- //ensure we get highest confidence, severity as there are likely multiple matches for this tag pattern
- foreach (TagInfo updateItem in result)
- {
- if (updateItem.Tag == tagItem)
- {
- Confidence oldConfidence;
- Enum.TryParse(updateItem.Confidence, out oldConfidence);
-
- if (match.Confidence > oldConfidence)
- {
- updateItem.Confidence = match.Confidence.ToString();
- pattern.Confidence = match.Confidence.ToString();
- }
-
- Severity oldSeverity;
- Enum.TryParse(updateItem.Severity, out oldSeverity);
- if (match.Severity > oldSeverity)
- {
- updateItem.Severity = match.Severity.ToString();
- }
-
- break;
- }
- }
- }
- }
- }
- }
- }
- else
- {
- foreach (var tagItem in _appMetaData?.UniqueTags ?? new List())
- {
- if (tagPatternRegex.IsMatch(tagItem) && !hashSet.Contains(pattern.SearchPattern))
+ if (!hashSet.Contains(pattern.SearchPattern))
{
result.Add(new TagInfo
{
Tag = tagItem,
+ Confidence = match.Confidence.ToString(),
+ Severity = match.Severity.ToString(),
ShortTag = pattern.DisplayName,
StatusIcon = pattern.DetectedIcon,
Detected = true
});
- hashSet.Add(tagItem);
+ hashSet.Add(pattern.SearchPattern);
+
+ pattern.Confidence = match.Confidence.ToString();
}
- }
- }
- }
- else if (addNotFound) //allow to report on false presense items
- {
- TagInfo tagInfo = new()
- {
- Tag = pattern.SearchPattern,
- Detected = false,
- ShortTag = pattern.DisplayName,
- StatusIcon = pattern.NotDetectedIcon,
- Confidence = "",
- Severity = ""
- };
-
- pattern.Confidence = "";
- result.Add(tagInfo);
- hashSet.Add(tagInfo.Tag);
- }
- }
-
- return result;
- }
-
- ///
- /// Gets a set of matching tags for a set of patterns, returning for all matches
- ///
- ///
- ///
- ///
- private List GetAllMatchingTagInfoList(TagGroup tagGroup, bool addNotFound = true)
- {
- List result = new();
- HashSet hashSet = new();
-
- foreach (TagSearchPattern pattern in tagGroup.Patterns ?? new List())
- {
- if (pattern.Detected)
- {
- var tagPatternRegex = pattern.Expression;
-
- foreach (var match in _appMetaData?.Matches ?? new List())
- {
- foreach (var tagItem in match.Tags ?? Array.Empty())
- {
- if (tagPatternRegex.IsMatch(tagItem))
+ else
{
- if (!hashSet.Contains(tagItem))
- {
- result.Add(new TagInfo
+ //ensure we get highest confidence, severity as there are likely multiple matches for this tag pattern
+ foreach (var updateItem in result)
+ if (updateItem.Tag == tagItem)
{
- Tag = tagItem,
- Confidence = match.Confidence.ToString(),
- Severity = match.Severity.ToString(),
- ShortTag = tagItem[(tagItem.LastIndexOf('.') + 1)..],
- StatusIcon = pattern.DetectedIcon,
- Detected = true
- });
+ Confidence oldConfidence;
+ Enum.TryParse(updateItem.Confidence, out oldConfidence);
- hashSet.Add(tagItem);
- }
- else
- { //ensure we have highest confidence, severity as there are likly multiple matches for this tag pattern
- foreach (TagInfo updateItem in result)
- {
- if (updateItem.Tag == tagItem)
+ if (match.Confidence > oldConfidence)
{
- Confidence oldConfidence;
- Enum.TryParse(updateItem.Confidence, out oldConfidence);
-
- if (match.Confidence > oldConfidence)
- {
- updateItem.Confidence = match.Confidence.ToString();
- pattern.Confidence = match.Confidence.ToString();
- }
-
- Severity oldSeverity;
- Enum.TryParse(updateItem.Severity, out oldSeverity);
- if (match.Severity > oldSeverity)
- {
- updateItem.Severity = match.Severity.ToString();
- }
-
- break;
+ updateItem.Confidence = match.Confidence.ToString();
+ pattern.Confidence = match.Confidence.ToString();
}
+
+ Severity oldSeverity;
+ Enum.TryParse(updateItem.Severity, out oldSeverity);
+ if (match.Severity > oldSeverity)
+ updateItem.Severity = match.Severity.ToString();
+
+ break;
}
- }
}
}
- }
- }
- }
-
- return result;
- }
-
- ///
- /// List of taginfo items ordered by name
- ///
- ///
- private List GetTagInfoListByName()
- {
- HashSet dupCheck = new();
- List result = new();
-
- foreach (string tag in _appMetaData?.UniqueTags ?? new List())
- {
- if (_appMetaData?.TotalMatchesCount > 0)
- {
- foreach (var match in _appMetaData?.Matches ?? new List())
- {
- foreach (string testTag in match.Tags ?? Array.Empty())
- {
- if (tag == testTag)
- {
- if (dupCheck.Add(testTag))
- {
- result.Add(new TagInfo
- {
- Tag = testTag,
- Confidence = match.Confidence.ToString(),
- Severity = match.Severity.ToString(),
- ShortTag = testTag[(testTag.LastIndexOf('.') + 1)..],
- });
-
- break;
- }
- }
- }
- }
}
else
{
- result.Add(new TagInfo
- {
- Tag = tag,
- ShortTag = tag[(tag.LastIndexOf('.') + 1)..],
- });
- }
- }
-
- return result;
- }
-
- ///
- /// Tags sorted by confidence
- ///
- ///
- private List GetTagInfoListByConfidence()
- {
- List result = new();
- HashSet dupCheck = new();
- RulesEngine.Confidence[] confidences = { Confidence.High, Confidence.Medium, Confidence.Low };
-
- foreach (string tag in _appMetaData?.UniqueTags ?? new List())
- {
- var searchPattern = new Regex(tag, RegexOptions.IgnoreCase);
- foreach (Confidence confidence in confidences)
- {
- foreach (var match in _appMetaData?.Matches ?? new List())
- {
- foreach (string testTag in match.Tags ?? Array.Empty())
+ foreach (var tagItem in _appMetaData?.UniqueTags ?? new List())
+ if (tagPatternRegex.IsMatch(tagItem) && !hashSet.Contains(pattern.SearchPattern))
{
- if (searchPattern.IsMatch(testTag))
+ result.Add(new TagInfo
{
- if (match.Confidence == confidence && dupCheck.Add(tag))
- {
- result.Add(new TagInfo
- {
- Tag = testTag,
- Confidence = confidence.ToString(),
- Severity = match.Severity.ToString(),
- ShortTag = testTag[(testTag.LastIndexOf('.') + 1)..],
- });
- }
- }
+ Tag = tagItem,
+ ShortTag = pattern.DisplayName,
+ StatusIcon = pattern.DetectedIcon,
+ Detected = true
+ });
+
+ hashSet.Add(tagItem);
}
- }
}
}
- return result;
- }
-
- ///
- /// Sorted by Severity
- ///
- ///
- private List GetTagInfoListBySeverity()
- {
- List result = new();
- HashSet dupCheck = new();
- Severity[] severities = { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview };
-
- foreach (string tag in _appMetaData?.UniqueTags ?? new List())
+ else if (addNotFound) //allow to report on false presense items
{
- var searchPattern = new Regex(tag, RegexOptions.IgnoreCase);
- foreach (Severity severity in severities)
+ TagInfo tagInfo = new()
{
- foreach (var match in _appMetaData?.Matches ?? new List())
- {
- foreach (string testTag in match.Tags ?? Array.Empty())
- {
- if (searchPattern.IsMatch(testTag))
- {
- if (match.Severity == severity && dupCheck.Add(tag))
- {
- result.Add(new TagInfo
- {
- Tag = testTag,
- Confidence = match.Confidence.ToString(),
- Severity = severity.ToString(),
- ShortTag = testTag[(testTag.LastIndexOf('.') + 1)..],
- });
- }
- }
- }
- }
- }
+ Tag = pattern.SearchPattern,
+ Detected = false,
+ ShortTag = pattern.DisplayName,
+ StatusIcon = pattern.NotDetectedIcon,
+ Confidence = "",
+ Severity = ""
+ };
+
+ pattern.Confidence = "";
+ result.Add(tagInfo);
+ hashSet.Add(tagInfo.Tag);
}
- return result;
- }
-
- ///
- /// Opportunity for any final data prep before report gen
- ///
- public List ConvertTagCounters(IEnumerable metricTagCounters)
- {
- List result = new();
- //TagCountersUI is liquid compatible while TagCounters is not to support json serialization; the split prevents exception
- //not fixable via json iteration disabling
-
- result.AddRange(metricTagCounters.Select(counter => new TagCounterUI()
- {
- Tag = counter.Tag,
- Count = counter.Count
- }));
-
- return result;
- }
-
- }
+ return result;
+ }
///
- /// Compatible for liquid; used to avoid use in MetaData as it adds unwanted properties to json serialization
+ /// Gets a set of matching tags for a set of patterns, returning for all matches
///
- public class TagCounterUI : Drop
+ ///
+ ///
+ ///
+ private List GetAllMatchingTagInfoList(TagGroup tagGroup, bool addNotFound = true)
{
- [JsonProperty(PropertyName = "tag")]
- public string? Tag { get; set; }
+ List result = new();
+ HashSet hashSet = new();
- [JsonProperty(PropertyName = "displayName")]
- public string? ShortTag { get; set; }
+ foreach (var pattern in tagGroup.Patterns ?? new List())
+ if (pattern.Detected)
+ {
+ var tagPatternRegex = pattern.Expression;
- [JsonProperty(PropertyName = "count")]
- public int Count { get; set; }
+ foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var tagItem in match.Tags ?? Array.Empty())
+ if (tagPatternRegex.IsMatch(tagItem))
+ {
+ if (!hashSet.Contains(tagItem))
+ {
+ result.Add(new TagInfo
+ {
+ Tag = tagItem,
+ Confidence = match.Confidence.ToString(),
+ Severity = match.Severity.ToString(),
+ ShortTag = tagItem[(tagItem.LastIndexOf('.') + 1)..],
+ StatusIcon = pattern.DetectedIcon,
+ Detected = true
+ });
- [JsonProperty(PropertyName = "includeAsMatch")]
- public bool IncludeAsMatch => false;
+ hashSet.Add(tagItem);
+ }
+ else
+ {
+ //ensure we have highest confidence, severity as there are likly multiple matches for this tag pattern
+ foreach (var updateItem in result)
+ if (updateItem.Tag == tagItem)
+ {
+ Confidence oldConfidence;
+ Enum.TryParse(updateItem.Confidence, out oldConfidence);
+
+ if (match.Confidence > oldConfidence)
+ {
+ updateItem.Confidence = match.Confidence.ToString();
+ pattern.Confidence = match.Confidence.ToString();
+ }
+
+ Severity oldSeverity;
+ Enum.TryParse(updateItem.Severity, out oldSeverity);
+ if (match.Severity > oldSeverity) updateItem.Severity = match.Severity.ToString();
+
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// List of taginfo items ordered by name
+ ///
+ ///
+ private List GetTagInfoListByName()
+ {
+ HashSet dupCheck = new();
+ List result = new();
+
+ foreach (var tag in _appMetaData?.UniqueTags ?? new List())
+ if (_appMetaData?.TotalMatchesCount > 0)
+ {
+ foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var testTag in match.Tags ?? Array.Empty())
+ if (tag == testTag)
+ if (dupCheck.Add(testTag))
+ {
+ result.Add(new TagInfo
+ {
+ Tag = testTag,
+ Confidence = match.Confidence.ToString(),
+ Severity = match.Severity.ToString(),
+ ShortTag = testTag[(testTag.LastIndexOf('.') + 1)..]
+ });
+
+ break;
+ }
+ }
+ else
+ {
+ result.Add(new TagInfo
+ {
+ Tag = tag,
+ ShortTag = tag[(tag.LastIndexOf('.') + 1)..]
+ });
+ }
+
+ return result;
+ }
+
+ ///
+ /// Tags sorted by confidence
+ ///
+ ///
+ private List GetTagInfoListByConfidence()
+ {
+ List result = new();
+ HashSet dupCheck = new();
+ Confidence[] confidences = { Confidence.High, Confidence.Medium, Confidence.Low };
+
+ foreach (var tag in _appMetaData?.UniqueTags ?? new List())
+ {
+ var searchPattern = new Regex(tag, RegexOptions.IgnoreCase);
+ foreach (var confidence in confidences)
+ foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var testTag in match.Tags ?? Array.Empty())
+ if (searchPattern.IsMatch(testTag))
+ if (match.Confidence == confidence && dupCheck.Add(tag))
+ result.Add(new TagInfo
+ {
+ Tag = testTag,
+ Confidence = confidence.ToString(),
+ Severity = match.Severity.ToString(),
+ ShortTag = testTag[(testTag.LastIndexOf('.') + 1)..]
+ });
+ }
+
+ return result;
+ }
+
+ ///
+ /// Sorted by Severity
+ ///
+ ///
+ private List GetTagInfoListBySeverity()
+ {
+ List result = new();
+ HashSet dupCheck = new();
+ Severity[] severities =
+ { Severity.Critical, Severity.Important, Severity.Moderate, Severity.BestPractice, Severity.ManualReview };
+
+ foreach (var tag in _appMetaData?.UniqueTags ?? new List())
+ {
+ var searchPattern = new Regex(tag, RegexOptions.IgnoreCase);
+ foreach (var severity in severities)
+ foreach (var match in _appMetaData?.Matches ?? new List())
+ foreach (var testTag in match.Tags ?? Array.Empty())
+ if (searchPattern.IsMatch(testTag))
+ if (match.Severity == severity && dupCheck.Add(tag))
+ result.Add(new TagInfo
+ {
+ Tag = testTag,
+ Confidence = match.Confidence.ToString(),
+ Severity = severity.ToString(),
+ ShortTag = testTag[(testTag.LastIndexOf('.') + 1)..]
+ });
+ }
+
+ return result;
+ }
+
+ ///
+ /// Opportunity for any final data prep before report gen
+ ///
+ public List ConvertTagCounters(IEnumerable metricTagCounters)
+ {
+ List result = new();
+ //TagCountersUI is liquid compatible while TagCounters is not to support json serialization; the split prevents exception
+ //not fixable via json iteration disabling
+
+ result.AddRange(metricTagCounters.Select(counter => new TagCounterUI
+ {
+ Tag = counter.Tag,
+ Count = counter.Count
+ }));
+
+ return result;
}
}
+
+///
+/// Compatible for liquid; used to avoid use in MetaData as it adds unwanted properties to json serialization
+///
+public class TagCounterUI : Drop
+{
+ [JsonProperty(PropertyName = "tag")] public string? Tag { get; set; }
+
+ [JsonProperty(PropertyName = "displayName")]
+ public string? ShortTag { get; set; }
+
+ [JsonProperty(PropertyName = "count")] public int Count { get; set; }
+
+ [JsonProperty(PropertyName = "includeAsMatch")]
+ public bool IncludeAsMatch => false;
+}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs b/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs
index 7eb8759..4b50bc2 100644
--- a/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs
+++ b/AppInspector.CLI/Writers/AnalyzeJsonWriter.cs
@@ -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;
+
+///
+/// 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...
+///
+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 _logger;
+
+ public AnalyzeJsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
+ {
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.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();
+ }
///
- /// 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
///
- public class AnalyzeJsonWriter : CommandResultsWriter
+ private class TagsFile
{
- private readonly ILogger _logger;
-
- public AnalyzeJsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
- {
- _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance;
- }
-
- ///
- /// simple wrapper for serializing results for simple tags only during processing
- ///
- 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; }
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs b/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs
index c4e0f2b..a9db41f 100644
--- a/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs
+++ b/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs
@@ -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? tagsToAdd)
{
- internal static void AddRange(this TagsCollection tc, IEnumerable? 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);
}
- ///
- /// Writes in sarif format
- ///
- public class AnalyzeSarifWriter : CommandResultsWriter
+}
+
+///
+/// Writes in sarif format
+///
+public class AnalyzeSarifWriter : CommandResultsWriter
+{
+ private readonly ILogger _logger;
+
+ public AnalyzeSarifWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
- private readonly ILogger _logger;
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.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() ?? NullLogger.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();
+ 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();
- 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
{
- run.VersionControlProvenance = new List()
+ new()
{
- new VersionControlDetails()
- {
- RepositoryUri = uri,
- RevisionId = cLIAnalyzeCmdOptions.CommitHash
- }
- };
- }
-
- var artifacts = new List();
- 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();
- run.Results = new List();
- foreach (var match in analyzeResult.Metadata.Matches)
+
+ var artifacts = new List();
+ 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();
+ run.Results = new List();
+ 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()
+ };
+ 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
+ {
+ 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
};
}
diff --git a/AppInspector.CLI/Writers/AnalyzeTextWriter.cs b/AppInspector.CLI/Writers/AnalyzeTextWriter.cs
index c0756ce..ee6140d 100644
--- a/AppInspector.CLI/Writers/AnalyzeTextWriter.cs
+++ b/AppInspector.CLI/Writers/AnalyzeTextWriter.cs
@@ -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 _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() ?? NullLogger.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()) WriteMatch(match);
+
+ if (autoClose) FlushAndClose();
+ }
+
+
+ private string StringList(IEnumerable data)
+ {
+ return string.Join(' ', data);
+ }
+
+ private string StringList(IDictionary data)
+ {
+ return string.Join(' ', data.Keys);
+ }
+
+ private string StringList(IDictionary 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())
- {
- WriteMatch(match);
- }
-
- if (autoClose)
- {
- FlushAndClose();
- }
+ build.Append(s);
+ build.Append(" ");
}
- public AnalyzeTextWriter(TextWriter textWriter, string formatString, ILoggerFactory? loggerFactory = null) : base(textWriter)
- {
- _logger = loggerFactory?.CreateLogger() ?? NullLogger.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 data)
- {
- return string.Join(' ', data);
- }
+ ///
+ /// even out delineator for headings
+ ///
+ ///
+ ///
+ 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 data)
- {
- return string.Join(' ', data.Keys);
- }
+ return build.ToString();
+ }
- private string StringList(IDictionary 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())}");
+ TextWriter.WriteLine($"Package types: {StringList(metaData.PackageTypes ?? new List())}");
+ TextWriter.WriteLine($"File extensions: {StringList(metaData.FileExtensions ?? new List())}");
+ TextWriter.WriteLine(string.Format(MakeHeading("Detetected Targets")));
+ TextWriter.WriteLine($"Output types: {StringList(metaData.Outputs ?? new List())}");
+ TextWriter.WriteLine($"OS Targets: {StringList(metaData.OSTargets ?? new List())}");
+ TextWriter.WriteLine($"CPU Targets: {StringList(metaData.CPUTargets ?? new List())}");
+ TextWriter.WriteLine($"Cloud targets: {StringList(metaData.CloudTargets ?? new List())}");
+ 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);
- ///
- /// even out delineator for headings
- ///
- ///
- ///
- 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())
+ 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()));
-
- 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())}");
- TextWriter.WriteLine($"Package types: {StringList(metaData.PackageTypes ?? new List())}");
- TextWriter.WriteLine($"File extensions: {StringList(metaData.FileExtensions ?? new List())}");
- TextWriter.WriteLine(string.Format(MakeHeading("Detetected Targets")));
- TextWriter.WriteLine($"Output types: {StringList(metaData.Outputs ?? new List())}");
- TextWriter.WriteLine($"OS Targets: {StringList(metaData.OSTargets ?? new List())}");
- TextWriter.WriteLine($"CPU Targets: {StringList(metaData.CPUTargets ?? new List())}");
- TextWriter.WriteLine($"Cloud targets: {StringList(metaData.CloudTargets ?? new List())}");
- 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())
- {
- 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()));
-
- 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())
- {
- TextWriter.WriteLine(s);
- }
- }
-
- private readonly string _formatString;
- private readonly ILogger _logger;
+ foreach (var s in metaData.UniqueDependencies ?? new List()) TextWriter.WriteLine(s);
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/CmdResultsWriter.cs b/AppInspector.CLI/Writers/CmdResultsWriter.cs
index c18394b..9688992 100644
--- a/AppInspector.CLI/Writers/CmdResultsWriter.cs
+++ b/AppInspector.CLI/Writers/CmdResultsWriter.cs
@@ -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;
- ///
- /// Common class for command specific writers
- ///
- public abstract class CommandResultsWriter
+namespace Microsoft.ApplicationInspector.CLI;
+
+///
+/// Common class for command specific writers
+///
+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();
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/ExportTagsTextWriter.cs b/AppInspector.CLI/Writers/ExportTagsTextWriter.cs
index 80e3c9b..96602b8 100644
--- a/AppInspector.CLI/Writers/ExportTagsTextWriter.cs
+++ b/AppInspector.CLI/Writers/ExportTagsTextWriter.cs
@@ -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 _logger;
- internal class ExportTagsTextWriter : CommandResultsWriter
+ internal ExportTagsTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
- private readonly ILogger _logger;
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.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() ?? NullLogger.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();
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/JsonWriter.cs b/AppInspector.CLI/Writers/JsonWriter.cs
index 8996274..d54bb6a 100644
--- a/AppInspector.CLI/Writers/JsonWriter.cs
+++ b/AppInspector.CLI/Writers/JsonWriter.cs
@@ -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 _logger;
- internal class JsonWriter : CommandResultsWriter
+ internal JsonWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
- private readonly ILogger _logger;
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.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() ?? NullLogger.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();
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/TagDiffTextWriter.cs b/AppInspector.CLI/Writers/TagDiffTextWriter.cs
index d50b26a..a0e360d 100644
--- a/AppInspector.CLI/Writers/TagDiffTextWriter.cs
+++ b/AppInspector.CLI/Writers/TagDiffTextWriter.cs
@@ -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 _logger;
- public class TagDiffTextWriter : CommandResultsWriter
+ public TagDiffTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
- private readonly ILogger _logger;
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.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() ?? NullLogger.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();
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/VerifyRulesTextWriter.cs b/AppInspector.CLI/Writers/VerifyRulesTextWriter.cs
index ad62236..c67403b 100644
--- a/AppInspector.CLI/Writers/VerifyRulesTextWriter.cs
+++ b/AppInspector.CLI/Writers/VerifyRulesTextWriter.cs
@@ -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 _logger;
- internal class VerifyRulesTextWriter : CommandResultsWriter
+ public VerifyRulesTextWriter(TextWriter textWriter, ILoggerFactory? loggerFactory = null) : base(textWriter)
{
- private readonly ILogger _logger;
+ _logger = loggerFactory?.CreateLogger() ?? NullLogger.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() ?? NullLogger.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();
}
}
\ No newline at end of file
diff --git a/AppInspector.CLI/Writers/WriterFactory.cs b/AppInspector.CLI/Writers/WriterFactory.cs
index 2a9c479..42bb783 100644
--- a/AppInspector.CLI/Writers/WriterFactory.cs
+++ b/AppInspector.CLI/Writers/WriterFactory.cs
@@ -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 _logger;
+ private readonly ILoggerFactory? _loggerFactory;
- public class WriterFactory
+ public WriterFactory(ILoggerFactory? loggerFactory = null)
{
- private readonly ILoggerFactory? _loggerFactory;
- private readonly ILogger _logger;
+ _loggerFactory = loggerFactory;
+ _logger = _loggerFactory?.CreateLogger