Skip to content

Commit

Permalink
Allow weird assembly name
Browse files Browse the repository at this point in the history
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
  • Loading branch information
exyi committed Nov 19, 2023
1 parent 2b6220a commit ff7c67d
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -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<BindingToken> tokens)
{
Tokens = tokens;
Name = string.Concat(tokens.Select(t => t.Text));
}

public AssemblyNameBindingParserNode(string name)
: this(new List<BindingToken>() { new BindingToken(name, BindingTokenType.Identifier, 0, 0, 0, 0)})
{ }

public override IEnumerable<BindingParserNode> EnumerateChildNodes()
=> Enumerable.Empty<BindingParserNode>();

public override string ToDisplayString() => $"{Name}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down
20 changes: 13 additions & 7 deletions src/Tests/Parser/Binding/BindingParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>", "Domain.Company.Product<int>")]
[DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company<int>.Product", "Domain.Company<int>.Product")]
[DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain<int>.Company.Product", "Domain<int>.Company.Product")]
[DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product<int>", "Product<int>")]

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<int>")]
[DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company<int>.Product")]
[DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain<int>.Company.Product")]
[DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product<int>")]
[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);
Expand Down

0 comments on commit ff7c67d

Please sign in to comment.