From 99c93ec5afe499a5080ab453293a1143fd7ae0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 22 Aug 2026 19:58:30 +0200 Subject: [PATCH] AVRO-4346: [csharp] Validate enum symbols and protocol names at parse time AVRO-4314 added Avro name-grammar validation for record/fixed/enum names, field names and aliases, and message names, but two parse-time paths were left unvalidated: - Enum symbols read from JSON: EnumSchema.NewInstance only checked for duplicates and never called ValidateSymbolName (that ran only on the programmatic Create() path). An out-of-spec symbol was carried through and emitted verbatim as an enum member identifier by the code generator. - Protocol names: Protocol.Parse never validated the protocol name, which is emitted as generated class names. Validate enum symbols on the JSON parse path (surfacing a SchemaParseException with the JSON path, consistent with the duplicate-symbol error) and validate the protocol name via SchemaName.ValidateName (surfacing a ProtocolParseException, consistent with message-name handling). Adds tests for out-of-spec enum symbols and protocol names. --- lang/csharp/src/apache/main/Protocol/Protocol.cs | 8 ++++++++ lang/csharp/src/apache/main/Schema/EnumSchema.cs | 9 +++++++++ lang/csharp/src/apache/test/Protocol/ProtocolTest.cs | 11 +++++++++++ lang/csharp/src/apache/test/Schema/SchemaTests.cs | 6 ++++++ 4 files changed, 34 insertions(+) diff --git a/lang/csharp/src/apache/main/Protocol/Protocol.cs b/lang/csharp/src/apache/main/Protocol/Protocol.cs index 94ae1268a0d..241fe13bb41 100644 --- a/lang/csharp/src/apache/main/Protocol/Protocol.cs +++ b/lang/csharp/src/apache/main/Protocol/Protocol.cs @@ -126,6 +126,14 @@ public static Protocol Parse(string jstring) private static Protocol Parse(JToken jtok) { string name = JsonHelper.GetRequiredString(jtok, "protocol"); + try + { + SchemaName.ValidateName(name, "protocol"); + } + catch (SchemaParseException e) + { + throw new ProtocolParseException($"Invalid protocol name: {name}", e); + } string space = JsonHelper.GetOptionalString(jtok, "namespace"); string doc = JsonHelper.GetOptionalString(jtok, "doc"); diff --git a/lang/csharp/src/apache/main/Schema/EnumSchema.cs b/lang/csharp/src/apache/main/Schema/EnumSchema.cs index 225780310a6..9bec3cca353 100644 --- a/lang/csharp/src/apache/main/Schema/EnumSchema.cs +++ b/lang/csharp/src/apache/main/Schema/EnumSchema.cs @@ -102,6 +102,15 @@ internal static EnumSchema NewInstance(JToken jtok, PropertyMap props, SchemaNam if (symbolMap.ContainsKey(s)) throw new SchemaParseException($"Duplicate symbol: {s} at '{jtok.Path}'"); + try + { + ValidateSymbolName(s); + } + catch (AvroException e) + { + throw new SchemaParseException($"{e.Message} at '{jtok.Path}'", e); + } + symbolMap[s] = i++; symbols.Add(s); } diff --git a/lang/csharp/src/apache/test/Protocol/ProtocolTest.cs b/lang/csharp/src/apache/test/Protocol/ProtocolTest.cs index 47f8d98ef00..1e1501a2f69 100644 --- a/lang/csharp/src/apache/test/Protocol/ProtocolTest.cs +++ b/lang/csharp/src/apache/test/Protocol/ProtocolTest.cs @@ -198,6 +198,17 @@ public static void TestInvalidMessageName(string str) Assert.Throws(() => Protocol.Parse(str)); } + [TestCase(@"{ ""protocol"": ""9bad"", ""types"": [], ""messages"": {} }", + TestName = "ProtocolNameLeadingDigit")] + [TestCase(@"{ ""protocol"": ""bad name"", ""types"": [], ""messages"": {} }", + TestName = "ProtocolNameWithSpace")] + [TestCase(@"{ ""protocol"": ""x; class Evil"", ""types"": [], ""messages"": {} }", + TestName = "ProtocolNameInjection")] + public static void TestInvalidProtocolName(string str) + { + Assert.Throws(() => Protocol.Parse(str)); + } + // Protocols match [TestCase( @"{ diff --git a/lang/csharp/src/apache/test/Schema/SchemaTests.cs b/lang/csharp/src/apache/test/Schema/SchemaTests.cs index d2958a82042..e20c2231811 100644 --- a/lang/csharp/src/apache/test/Schema/SchemaTests.cs +++ b/lang/csharp/src/apache/test/Schema/SchemaTests.cs @@ -85,6 +85,12 @@ public class SchemaTests typeof(SchemaParseException), Description = "No name")] [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\" : [\"AA\", \"AA\"]}", typeof(SchemaParseException), Description = "Duplicate symbol")] + [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\" : [\"1A\"]}", + typeof(SchemaParseException), Description = "Symbol with leading digit")] + [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\" : [\"A B\"]}", + typeof(SchemaParseException), Description = "Symbol with space")] + [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\" : [\"A-B\"]}", + typeof(SchemaParseException), Description = "Symbol with hyphen")] // Array [TestCase("{\"type\": \"array\", \"items\": \"long\"}")]