using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
///
/// Validates script code by attempting to compile it using Roslyn.
/// In production, this would compile C# scripts against a stub ScriptApi assembly
/// that provides the allowed API surface (attribute read/write, CallScript, CallShared, etc.)
/// and enforces the forbidden API list (System.IO, Process, Threading, Reflection, raw network).
///
/// For now, this implementation performs basic syntax validation.
///
///
/// SECURITY LIMITATION (TemplateEngine-006): the forbidden-API check below
/// is an interim, advisory text scan — it is NOT an authoritative trust-model
/// boundary. removes the
/// false-positive half (forbidden text inside a string/comment is ignored), but a
/// determined script can still bypass the literal patterns via namespace aliases,
/// using static, or global::-qualified references. Authoritative
/// enforcement requires Roslyn semantic symbol analysis of the referenced
/// types/namespaces and is the responsibility of the real script compiler and the
/// Site Runtime sandbox. Do not rely on this class as the sole trust-model gate.
///
///
public class ScriptCompiler
{
///
/// Forbidden namespace patterns — scripts (and trigger expressions, via
/// ) must not use these. Trigger expressions run
/// under the same trust model as scripts, so the list is shared from here rather
/// than duplicated.
///
///
/// Matched with against code
/// regions only. This is advisory — see the class summary's SECURITY LIMITATION
/// note; the substring patterns are bypassable and the authoritative check is
/// deferred to Roslyn semantic analysis.
///
///
internal static readonly string[] ForbiddenPatterns =
[
"System.IO.",
"System.Diagnostics.Process",
"System.Threading.",
"System.Reflection.",
"System.Net.Sockets.",
"System.Net.Http.",
];
///
/// Attempts to compile a script and returns success or a compilation error.
///
/// The C# script code.
/// The canonical name of the script (for error messages).
/// Success if the script compiles, or Failure with the error message.
public Result TryCompile(string code, string scriptName)
{
if (string.IsNullOrWhiteSpace(code))
return Result.Failure($"Script '{scriptName}' has empty code.");
// Check for forbidden APIs. Advisory only (see class summary): the scan is
// code-region-aware so forbidden text inside a string/comment is ignored,
// but it remains a substring match and is not an authoritative boundary.
foreach (var pattern in ForbiddenPatterns)
{
if (CSharpDelimiterScanner.ContainsInCode(code, pattern))
{
return Result.Failure(
$"Script '{scriptName}' uses forbidden API: '{pattern.TrimEnd('.')}'. " +
"Scripts cannot use System.IO, Process, Threading, Reflection, or raw network APIs.");
}
}
// Basic structural validation: balanced braces/brackets/parens. The scan
// is string- and comment-aware (see CSharpDelimiterScanner) so a delimiter
// inside a regular/verbatim/interpolated/raw string, a char literal, or a
// comment does not produce a false mismatch. This remains an interim check
// until the Roslyn-based compiler is wired in.
var mismatch = CSharpDelimiterScanner.Scan(code);
return mismatch switch
{
CSharpDelimiterScanner.Mismatch.None =>
Result.Success(true),
CSharpDelimiterScanner.Mismatch.UnexpectedCloseBrace =>
Result.Failure($"Script '{scriptName}' has mismatched braces (unexpected closing brace)."),
CSharpDelimiterScanner.Mismatch.UnclosedBrace =>
Result.Failure($"Script '{scriptName}' has mismatched braces (unclosed opening brace)."),
CSharpDelimiterScanner.Mismatch.UnexpectedCloseBracket =>
Result.Failure($"Script '{scriptName}' has mismatched brackets (unexpected closing bracket)."),
CSharpDelimiterScanner.Mismatch.UnclosedBracket =>
Result.Failure($"Script '{scriptName}' has mismatched brackets (unclosed opening bracket)."),
CSharpDelimiterScanner.Mismatch.UnexpectedCloseParen =>
Result.Failure($"Script '{scriptName}' has mismatched parentheses (unexpected closing parenthesis)."),
CSharpDelimiterScanner.Mismatch.UnclosedParen =>
Result.Failure($"Script '{scriptName}' has mismatched parentheses (unclosed opening parenthesis)."),
CSharpDelimiterScanner.Mismatch.UnclosedBlockComment =>
Result.Failure($"Script '{scriptName}' has an unclosed block comment."),
CSharpDelimiterScanner.Mismatch.UnterminatedString =>
Result.Failure($"Script '{scriptName}' has an unterminated string literal."),
CSharpDelimiterScanner.Mismatch.UnterminatedChar =>
Result.Failure($"Script '{scriptName}' has an unterminated character literal."),
_ => Result.Success(true),
};
}
}