| Index: third_party/protobuf/csharp/src/Google.Protobuf/JsonParser.cs
|
| diff --git a/third_party/protobuf/csharp/src/Google.Protobuf/JsonParser.cs b/third_party/protobuf/csharp/src/Google.Protobuf/JsonParser.cs
|
| index 95f9ad351ebcacc3ce6ec2cee0fd761a2baa8ca6..d738ebb04b17449cce9a263a4b267c31c0a2c358 100644
|
| --- a/third_party/protobuf/csharp/src/Google.Protobuf/JsonParser.cs
|
| +++ b/third_party/protobuf/csharp/src/Google.Protobuf/JsonParser.cs
|
| @@ -168,6 +168,10 @@ namespace Google.Protobuf
|
| }
|
| var descriptor = message.Descriptor;
|
| var jsonFieldMap = descriptor.Fields.ByJsonName();
|
| + // All the oneof fields we've already accounted for - we can only see each of them once.
|
| + // The set is created lazily to avoid the overhead of creating a set for every message
|
| + // we parsed, when oneofs are relatively rare.
|
| + HashSet<OneofDescriptor> seenOneofs = null;
|
| while (true)
|
| {
|
| token = tokenizer.Next();
|
| @@ -183,6 +187,17 @@ namespace Google.Protobuf
|
| FieldDescriptor field;
|
| if (jsonFieldMap.TryGetValue(name, out field))
|
| {
|
| + if (field.ContainingOneof != null)
|
| + {
|
| + if (seenOneofs == null)
|
| + {
|
| + seenOneofs = new HashSet<OneofDescriptor>();
|
| + }
|
| + if (!seenOneofs.Add(field.ContainingOneof))
|
| + {
|
| + throw new InvalidProtocolBufferException($"Multiple values specified for oneof {field.ContainingOneof.Name}");
|
| + }
|
| + }
|
| MergeField(message, field, tokenizer);
|
| }
|
| else
|
| @@ -200,10 +215,15 @@ namespace Google.Protobuf
|
| var token = tokenizer.Next();
|
| if (token.Type == JsonToken.TokenType.Null)
|
| {
|
| + // Clear the field if we see a null token, unless it's for a singular field of type
|
| + // google.protobuf.Value.
|
| // Note: different from Java API, which just ignores it.
|
| // TODO: Bring it more in line? Discuss...
|
| - field.Accessor.Clear(message);
|
| - return;
|
| + if (field.IsMap || field.IsRepeated || !IsGoogleProtobufValueField(field))
|
| + {
|
| + field.Accessor.Clear(message);
|
| + return;
|
| + }
|
| }
|
| tokenizer.PushBack(token);
|
|
|
| @@ -239,6 +259,10 @@ namespace Google.Protobuf
|
| return;
|
| }
|
| tokenizer.PushBack(token);
|
| + if (token.Type == JsonToken.TokenType.Null)
|
| + {
|
| + throw new InvalidProtocolBufferException("Repeated field elements cannot be null");
|
| + }
|
| list.Add(ParseSingleValue(field, tokenizer));
|
| }
|
| }
|
| @@ -270,19 +294,30 @@ namespace Google.Protobuf
|
| }
|
| object key = ParseMapKey(keyField, token.StringValue);
|
| object value = ParseSingleValue(valueField, tokenizer);
|
| - // TODO: Null handling
|
| + if (value == null)
|
| + {
|
| + throw new InvalidProtocolBufferException("Map values must not be null");
|
| + }
|
| dictionary[key] = value;
|
| }
|
| }
|
|
|
| + private static bool IsGoogleProtobufValueField(FieldDescriptor field)
|
| + {
|
| + return field.FieldType == FieldType.Message &&
|
| + field.MessageType.FullName == Value.Descriptor.FullName;
|
| + }
|
| +
|
| private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
|
| {
|
| var token = tokenizer.Next();
|
| if (token.Type == JsonToken.TokenType.Null)
|
| {
|
| - if (field.FieldType == FieldType.Message && field.MessageType.FullName == Value.Descriptor.FullName)
|
| + // TODO: In order to support dynamic messages, we should really build this up
|
| + // dynamically.
|
| + if (IsGoogleProtobufValueField(field))
|
| {
|
| - return new Value { NullValue = NullValue.NULL_VALUE };
|
| + return Value.ForNull();
|
| }
|
| return null;
|
| }
|
| @@ -339,7 +374,7 @@ namespace Google.Protobuf
|
| /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
|
| public T Parse<T>(string json) where T : IMessage, new()
|
| {
|
| - Preconditions.CheckNotNull(json, nameof(json));
|
| + ProtoPreconditions.CheckNotNull(json, nameof(json));
|
| return Parse<T>(new StringReader(json));
|
| }
|
|
|
| @@ -352,7 +387,7 @@ namespace Google.Protobuf
|
| /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
|
| public T Parse<T>(TextReader jsonReader) where T : IMessage, new()
|
| {
|
| - Preconditions.CheckNotNull(jsonReader, nameof(jsonReader));
|
| + ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader));
|
| T message = new T();
|
| Merge(message, jsonReader);
|
| return message;
|
| @@ -367,8 +402,8 @@ namespace Google.Protobuf
|
| /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
|
| public IMessage Parse(string json, MessageDescriptor descriptor)
|
| {
|
| - Preconditions.CheckNotNull(json, nameof(json));
|
| - Preconditions.CheckNotNull(descriptor, nameof(descriptor));
|
| + ProtoPreconditions.CheckNotNull(json, nameof(json));
|
| + ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));
|
| return Parse(new StringReader(json), descriptor);
|
| }
|
|
|
| @@ -381,8 +416,8 @@ namespace Google.Protobuf
|
| /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
|
| public IMessage Parse(TextReader jsonReader, MessageDescriptor descriptor)
|
| {
|
| - Preconditions.CheckNotNull(jsonReader, nameof(jsonReader));
|
| - Preconditions.CheckNotNull(descriptor, nameof(descriptor));
|
| + ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader));
|
| + ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));
|
| IMessage message = descriptor.Parser.CreateTemplate();
|
| Merge(message, jsonReader);
|
| return message;
|
| @@ -464,6 +499,11 @@ namespace Google.Protobuf
|
| {
|
| tokens.Add(token);
|
| token = tokenizer.Next();
|
| +
|
| + if (tokenizer.ObjectDepth < typeUrlObjectDepth)
|
| + {
|
| + throw new InvalidProtocolBufferException("Any message with no @type");
|
| + }
|
| }
|
|
|
| // Don't add the @type property or its value to the recorded token list
|
| @@ -473,7 +513,7 @@ namespace Google.Protobuf
|
| throw new InvalidProtocolBufferException("Expected string value for Any.@type");
|
| }
|
| string typeUrl = token.StringValue;
|
| - string typeName = JsonFormatter.GetTypeName(typeUrl);
|
| + string typeName = Any.GetTypeName(typeUrl);
|
|
|
| MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
|
| if (descriptor == null)
|
| @@ -540,17 +580,17 @@ namespace Google.Protobuf
|
| case FieldType.Int32:
|
| case FieldType.SInt32:
|
| case FieldType.SFixed32:
|
| - return ParseNumericString(keyText, int.Parse, false);
|
| + return ParseNumericString(keyText, int.Parse);
|
| case FieldType.UInt32:
|
| case FieldType.Fixed32:
|
| - return ParseNumericString(keyText, uint.Parse, false);
|
| + return ParseNumericString(keyText, uint.Parse);
|
| case FieldType.Int64:
|
| case FieldType.SInt64:
|
| case FieldType.SFixed64:
|
| - return ParseNumericString(keyText, long.Parse, false);
|
| + return ParseNumericString(keyText, long.Parse);
|
| case FieldType.UInt64:
|
| case FieldType.Fixed64:
|
| - return ParseNumericString(keyText, ulong.Parse, false);
|
| + return ParseNumericString(keyText, ulong.Parse);
|
| default:
|
| throw new InvalidProtocolBufferException("Invalid field type for map: " + field.FieldType);
|
| }
|
| @@ -561,7 +601,6 @@ namespace Google.Protobuf
|
| double value = token.NumberValue;
|
| checked
|
| {
|
| - // TODO: Validate that it's actually an integer, possibly in terms of the textual representation?
|
| try
|
| {
|
| switch (field.FieldType)
|
| @@ -569,16 +608,20 @@ namespace Google.Protobuf
|
| case FieldType.Int32:
|
| case FieldType.SInt32:
|
| case FieldType.SFixed32:
|
| + CheckInteger(value);
|
| return (int) value;
|
| case FieldType.UInt32:
|
| case FieldType.Fixed32:
|
| + CheckInteger(value);
|
| return (uint) value;
|
| case FieldType.Int64:
|
| case FieldType.SInt64:
|
| case FieldType.SFixed64:
|
| + CheckInteger(value);
|
| return (long) value;
|
| case FieldType.UInt64:
|
| case FieldType.Fixed64:
|
| + CheckInteger(value);
|
| return (ulong) value;
|
| case FieldType.Double:
|
| return value;
|
| @@ -597,20 +640,37 @@ namespace Google.Protobuf
|
| {
|
| return float.NegativeInfinity;
|
| }
|
| - throw new InvalidProtocolBufferException("Value out of range: " + value);
|
| + throw new InvalidProtocolBufferException($"Value out of range: {value}");
|
| }
|
| return (float) value;
|
| + case FieldType.Enum:
|
| + CheckInteger(value);
|
| + // Just return it as an int, and let the CLR convert it.
|
| + // Note that we deliberately don't check that it's a known value.
|
| + return (int) value;
|
| default:
|
| - throw new InvalidProtocolBufferException("Unsupported conversion from JSON number for field type " + field.FieldType);
|
| + throw new InvalidProtocolBufferException($"Unsupported conversion from JSON number for field type {field.FieldType}");
|
| }
|
| }
|
| catch (OverflowException)
|
| {
|
| - throw new InvalidProtocolBufferException("Value out of range: " + value);
|
| + throw new InvalidProtocolBufferException($"Value out of range: {value}");
|
| }
|
| }
|
| }
|
|
|
| + private static void CheckInteger(double value)
|
| + {
|
| + if (double.IsInfinity(value) || double.IsNaN(value))
|
| + {
|
| + throw new InvalidProtocolBufferException($"Value not an integer: {value}");
|
| + }
|
| + if (value != Math.Floor(value))
|
| + {
|
| + throw new InvalidProtocolBufferException($"Value not an integer: {value}");
|
| + }
|
| + }
|
| +
|
| private static object ParseSingleStringValue(FieldDescriptor field, string text)
|
| {
|
| switch (field.FieldType)
|
| @@ -618,47 +678,46 @@ namespace Google.Protobuf
|
| case FieldType.String:
|
| return text;
|
| case FieldType.Bytes:
|
| - return ByteString.FromBase64(text);
|
| + try
|
| + {
|
| + return ByteString.FromBase64(text);
|
| + }
|
| + catch (FormatException e)
|
| + {
|
| + throw InvalidProtocolBufferException.InvalidBase64(e);
|
| + }
|
| case FieldType.Int32:
|
| case FieldType.SInt32:
|
| case FieldType.SFixed32:
|
| - return ParseNumericString(text, int.Parse, false);
|
| + return ParseNumericString(text, int.Parse);
|
| case FieldType.UInt32:
|
| case FieldType.Fixed32:
|
| - return ParseNumericString(text, uint.Parse, false);
|
| + return ParseNumericString(text, uint.Parse);
|
| case FieldType.Int64:
|
| case FieldType.SInt64:
|
| case FieldType.SFixed64:
|
| - return ParseNumericString(text, long.Parse, false);
|
| + return ParseNumericString(text, long.Parse);
|
| case FieldType.UInt64:
|
| case FieldType.Fixed64:
|
| - return ParseNumericString(text, ulong.Parse, false);
|
| + return ParseNumericString(text, ulong.Parse);
|
| case FieldType.Double:
|
| - double d = ParseNumericString(text, double.Parse, true);
|
| - // double.Parse can return +/- infinity on Mono for non-infinite values which are out of range for double.
|
| - if (double.IsInfinity(d) && !text.Contains("Infinity"))
|
| - {
|
| - throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
|
| - }
|
| + double d = ParseNumericString(text, double.Parse);
|
| + ValidateInfinityAndNan(text, double.IsPositiveInfinity(d), double.IsNegativeInfinity(d), double.IsNaN(d));
|
| return d;
|
| case FieldType.Float:
|
| - float f = ParseNumericString(text, float.Parse, true);
|
| - // float.Parse can return +/- infinity on Mono for non-infinite values which are out of range for float.
|
| - if (float.IsInfinity(f) && !text.Contains("Infinity"))
|
| - {
|
| - throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
|
| - }
|
| + float f = ParseNumericString(text, float.Parse);
|
| + ValidateInfinityAndNan(text, float.IsPositiveInfinity(f), float.IsNegativeInfinity(f), float.IsNaN(f));
|
| return f;
|
| case FieldType.Enum:
|
| var enumValue = field.EnumType.FindValueByName(text);
|
| if (enumValue == null)
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid enum value: " + text + " for enum type: " + field.EnumType.FullName);
|
| + throw new InvalidProtocolBufferException($"Invalid enum value: {text} for enum type: {field.EnumType.FullName}");
|
| }
|
| // Just return it as an int, and let the CLR convert it.
|
| return enumValue.Number;
|
| default:
|
| - throw new InvalidProtocolBufferException("Unsupported conversion from JSON string for field type " + field.FieldType);
|
| + throw new InvalidProtocolBufferException($"Unsupported conversion from JSON string for field type {field.FieldType}");
|
| }
|
| }
|
|
|
| @@ -670,43 +729,53 @@ namespace Google.Protobuf
|
| return field.MessageType.Parser.CreateTemplate();
|
| }
|
|
|
| - private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser, bool floatingPoint)
|
| + private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser)
|
| {
|
| - // TODO: Prohibit leading zeroes (but allow 0!)
|
| - // TODO: Validate handling of "Infinity" etc. (Should be case sensitive, no leading whitespace etc)
|
| // Can't prohibit this with NumberStyles.
|
| if (text.StartsWith("+"))
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
|
| + throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
|
| }
|
| if (text.StartsWith("0") && text.Length > 1)
|
| {
|
| if (text[1] >= '0' && text[1] <= '9')
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
|
| + throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
|
| }
|
| }
|
| else if (text.StartsWith("-0") && text.Length > 2)
|
| {
|
| if (text[2] >= '0' && text[2] <= '9')
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
|
| + throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
|
| }
|
| }
|
| try
|
| {
|
| - var styles = floatingPoint
|
| - ? NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent
|
| - : NumberStyles.AllowLeadingSign;
|
| - return parser(text, styles, CultureInfo.InvariantCulture);
|
| + return parser(text, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
|
| }
|
| catch (FormatException)
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid numeric value for type: " + text);
|
| + throw new InvalidProtocolBufferException($"Invalid numeric value for type: {text}");
|
| }
|
| catch (OverflowException)
|
| {
|
| - throw new InvalidProtocolBufferException("Value out of range: " + text);
|
| + throw new InvalidProtocolBufferException($"Value out of range: {text}");
|
| + }
|
| + }
|
| +
|
| + /// <summary>
|
| + /// Checks that any infinite/NaN values originated from the correct text.
|
| + /// This corrects the lenient whitespace handling of double.Parse/float.Parse, as well as the
|
| + /// way that Mono parses out-of-range values as infinity.
|
| + /// </summary>
|
| + private static void ValidateInfinityAndNan(string text, bool isPositiveInfinity, bool isNegativeInfinity, bool isNaN)
|
| + {
|
| + if ((isPositiveInfinity && text != "Infinity") ||
|
| + (isNegativeInfinity && text != "-Infinity") ||
|
| + (isNaN && text != "NaN"))
|
| + {
|
| + throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");
|
| }
|
| }
|
|
|
| @@ -719,7 +788,7 @@ namespace Google.Protobuf
|
| var match = TimestampRegex.Match(token.StringValue);
|
| if (!match.Success)
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
|
| + throw new InvalidProtocolBufferException($"Invalid Timestamp value: {token.StringValue}");
|
| }
|
| var dateTime = match.Groups["datetime"].Value;
|
| var subseconds = match.Groups["subseconds"].Value;
|
| @@ -809,28 +878,24 @@ namespace Google.Protobuf
|
|
|
| try
|
| {
|
| - long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture);
|
| + long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture) * multiplier;
|
| int nanos = 0;
|
| if (subseconds != "")
|
| {
|
| // This should always work, as we've got 1-9 digits.
|
| int parsedFraction = int.Parse(subseconds.Substring(1));
|
| - nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length];
|
| + nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length] * multiplier;
|
| }
|
| - if (seconds >= Duration.MaxSeconds)
|
| + if (!Duration.IsNormalized(seconds, nanos))
|
| {
|
| - // Allow precisely 315576000000 seconds, but prohibit even 1ns more.
|
| - if (seconds > Duration.MaxSeconds || nanos > 0)
|
| - {
|
| - throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
|
| - }
|
| + throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}");
|
| }
|
| - message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds * multiplier);
|
| - message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos * multiplier);
|
| + message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds);
|
| + message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos);
|
| }
|
| catch (FormatException)
|
| {
|
| - throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
|
| + throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}");
|
| }
|
| }
|
|
|
| @@ -853,6 +918,8 @@ namespace Google.Protobuf
|
| private static string ToSnakeCase(string text)
|
| {
|
| var builder = new StringBuilder(text.Length * 2);
|
| + // Note: this is probably unnecessary now, but currently retained to be as close as possible to the
|
| + // C++, whilst still throwing an exception on underscores.
|
| bool wasNotUnderscore = false; // Initialize to false for case 1 (below)
|
| bool wasNotCap = false;
|
|
|
| @@ -886,7 +953,11 @@ namespace Google.Protobuf
|
| else
|
| {
|
| builder.Append(c);
|
| - wasNotUnderscore = c != '_';
|
| + if (c == '_')
|
| + {
|
| + throw new InvalidProtocolBufferException($"Invalid field mask: {text}");
|
| + }
|
| + wasNotUnderscore = true;
|
| wasNotCap = true;
|
| }
|
| }
|
| @@ -940,7 +1011,7 @@ namespace Google.Protobuf
|
| public Settings(int recursionLimit, TypeRegistry typeRegistry)
|
| {
|
| RecursionLimit = recursionLimit;
|
| - TypeRegistry = Preconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));
|
| + TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));
|
| }
|
| }
|
| }
|
|
|