From ff7c67d55147a1bfdb810bb812f39ed0a368f631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Sun, 19 Nov 2023 22:06:31 +0100 Subject: [PATCH] Allow weird assembly name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I didn't find and documented restrictions on the assembly names. If we take the new AssemblyName(...) function as reference implementation, basically only restrictions are no `,`, `=`, `/`, `\` and nullbytes (invalid UTF-16 is also allowed 🤦 :D) So this allows whatever we can allow. Assembly name is always at the end, so there is no point in placing restrictions on it. resolves #1728 --- .../Parser/AssemblyNameBindingParserNode.cs | 30 ++++++++++ .../AssemblyQualifiedNameBindingParserNode.cs | 4 +- .../Parser/Binding/Parser/BindingParser.cs | 55 ++++++++++--------- .../Parser/Binding/BindingParserTests.cs | 20 ++++--- 4 files changed, 75 insertions(+), 34 deletions(-) create mode 100644 src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyNameBindingParserNode.cs diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyNameBindingParserNode.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyNameBindingParserNode.cs new file mode 100644 index 0000000000..781cb28972 --- /dev/null +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyNameBindingParserNode.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotVVM.Framework.Compilation.Parser.Binding.Tokenizer; +using System.Diagnostics; +using System.Collections.Immutable; + +namespace DotVVM.Framework.Compilation.Parser.Binding.Parser +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class AssemblyNameBindingParserNode : BindingParserNode + { + public string Name { get; } + public AssemblyNameBindingParserNode(List tokens) + { + Tokens = tokens; + Name = string.Concat(tokens.Select(t => t.Text)); + } + + public AssemblyNameBindingParserNode(string name) + : this(new List() { new BindingToken(name, BindingTokenType.Identifier, 0, 0, 0, 0)}) + { } + + public override IEnumerable EnumerateChildNodes() + => Enumerable.Empty(); + + public override string ToDisplayString() => $"{Name}"; + } +} diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyQualifiedNameBindingParserNode.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyQualifiedNameBindingParserNode.cs index 5e2bdcc9b5..f57ba9047f 100644 --- a/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyQualifiedNameBindingParserNode.cs +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/AssemblyQualifiedNameBindingParserNode.cs @@ -8,9 +8,9 @@ namespace DotVVM.Framework.Compilation.Parser.Binding.Parser public class AssemblyQualifiedNameBindingParserNode: BindingParserNode { public BindingParserNode TypeName { get; } - public BindingParserNode AssemblyName { get; } + public AssemblyNameBindingParserNode AssemblyName { get; } - public AssemblyQualifiedNameBindingParserNode(BindingParserNode typeName, BindingParserNode assemblyName) + public AssemblyQualifiedNameBindingParserNode(BindingParserNode typeName, AssemblyNameBindingParserNode assemblyName) { this.TypeName = typeName; this.AssemblyName = assemblyName; diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs index 566206a076..78e1444665 100644 --- a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using DotVVM.Framework.Utils; namespace DotVVM.Framework.Compilation.Parser.Binding.Parser { @@ -128,31 +129,7 @@ public BindingParserNode ReadDirectiveTypeName() if (PeekType() == BindingTokenType.Comma) { Read(); - var assemblyName = ReadNamespaceOrTypeName(); - - // SimpleNameBinding means that assembly name does not contain dots - // MemberAccessBinding means that assembly name is complex (multiple identifiers delimited with dots) - if (!(assemblyName is SimpleNameBindingParserNode || assemblyName is MemberAccessBindingParserNode)) - { - assemblyName.NodeErrors.Add($"Expected assembly name but instead got {assemblyName.GetType().Name}."); - } - else if (assemblyName is MemberAccessBindingParserNode) - { - // Make sure there is no GenericNameBinding within assemblyName - var assemblyBinding = assemblyName; - while (assemblyBinding is MemberAccessBindingParserNode assemblyMemberBinding) - { - var memberExprType = assemblyMemberBinding.MemberNameExpression.GetType(); - var targetExprType = assemblyMemberBinding.TargetExpression.GetType(); - if (memberExprType == typeof(GenericTypeReferenceBindingParserNode) || targetExprType == typeof(GenericTypeReferenceBindingParserNode)) - { - assemblyName.NodeErrors.Add($"Generic identifier name is not allowed in an assembly name."); - break; - } - - assemblyBinding = assemblyMemberBinding.TargetExpression; - } - } + var assemblyName = ReadAssemblyName(); return new AssemblyQualifiedNameBindingParserNode(typeName, assemblyName); } @@ -163,6 +140,34 @@ public BindingParserNode ReadDirectiveTypeName() return typeName; } + public AssemblyNameBindingParserNode ReadAssemblyName() + { + // almost anything can be an assembly name, so we just read everything until the end + SkipWhiteSpace(); + var tokens = this.Tokens.Skip(CurrentIndex).ToList(); + CurrentIndex = Tokens.Count; + + while (tokens.Count > 0 && tokens[tokens.Count - 1].Type == BindingTokenType.WhiteSpace) + tokens.RemoveAt(tokens.Count - 1); + + var node = new AssemblyNameBindingParserNode(tokens); + if (node.Name.Length == 0) + { + node.NodeErrors.Add("Assembly name cannot be empty."); + return node; + } + try + { + new AssemblyName(node.Name); + } + catch (Exception ex) + { + node.NodeErrors.Add($"'{node.Name}' is invalid assembly name ({ex.Message})"); + return node; + } + return node; + } + public BindingParserNode ReadNamespaceOrTypeName() { return ReadIdentifierExpression(true); diff --git a/src/Tests/Parser/Binding/BindingParserTests.cs b/src/Tests/Parser/Binding/BindingParserTests.cs index 56dca98662..3c43757760 100644 --- a/src/Tests/Parser/Binding/BindingParserTests.cs +++ b/src/Tests/Parser/Binding/BindingParserTests.cs @@ -859,20 +859,26 @@ public void BindingParser_ArrayType_ValidAssemblyName(string binding) } [TestMethod] - [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product")] - [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product")] - public void BindingParser_AssemblyQualifiedName_ValidAssemblyName(string binding) + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product", "Domain.Company.Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product", "Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, My-Assembly-Name", "My-Assembly-Name")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product", "Domain.Company.Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product", "Domain.Company.Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product", "Domain.Company.Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product", "Product")] + + public void BindingParser_AssemblyQualifiedName_ValidAssemblyName(string binding, string assemblyName) { var parser = bindingParserNodeFactory.SetupParser(binding); var node = parser.ReadDirectiveTypeName() as AssemblyQualifiedNameBindingParserNode; Assert.IsFalse(node.AssemblyName.HasNodeErrors); + Assert.AreEqual(assemblyName, node.AssemblyName.ToDisplayString()); } [TestMethod] - [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product")] - [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product")] - [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product")] - [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, ")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, ,,,,,,,,,")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type, this/is/apparently/invalid/for/some/reason")] public void BindingParser_AssemblyQualifiedName_InvalidAssemblyName(string binding) { var parser = bindingParserNodeFactory.SetupParser(binding);