| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018 | #region Copyright notice and license// Protocol Buffers - Google's data interchange format// Copyright 2015 Google Inc.  All rights reserved.// https://developers.google.com/protocol-buffers///// Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions are// met:////     * Redistributions of source code must retain the above copyright// notice, this list of conditions and the following disclaimer.//     * Redistributions in binary form must reproduce the above// copyright notice, this list of conditions and the following disclaimer// in the documentation and/or other materials provided with the// distribution.//     * Neither the name of Google Inc. nor the names of its// contributors may be used to endorse or promote products derived from// this software without specific prior written permission.//// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.#endregionusing Google.Protobuf.Reflection;using Google.Protobuf.WellKnownTypes;using System;using System.Collections;using System.Collections.Generic;using System.Globalization;using System.IO;using System.Text;using System.Text.RegularExpressions;namespace Google.Protobuf{    /// <summary>    /// Reflection-based converter from JSON to messages.    /// </summary>    /// <remarks>    /// <para>    /// Instances of this class are thread-safe, with no mutable state.    /// </para>    /// <para>    /// This is a simple start to get JSON parsing working. As it's reflection-based,    /// it's not as quick as baking calls into generated messages - but is a simpler implementation.    /// (This code is generally not heavily optimized.)    /// </para>    /// </remarks>    public sealed class JsonParser    {        // Note: using 0-9 instead of \d to ensure no non-ASCII digits.        // This regex isn't a complete validator, but will remove *most* invalid input. We rely on parsing to do the rest.        private static readonly Regex TimestampRegex = new Regex(@"^(?<datetime>[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9])(?<subseconds>\.[0-9]{1,9})?(?<offset>(Z|[+-][0-1][0-9]:[0-5][0-9]))$", FrameworkPortability.CompiledRegexWhereAvailable);        private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledRegexWhereAvailable);        private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };        private static readonly char[] FieldMaskPathSeparators = new[] { ',' };        private static readonly JsonParser defaultInstance = new JsonParser(Settings.Default);        // TODO: Consider introducing a class containing parse state of the parser, tokenizer and depth. That would simplify these handlers        // and the signatures of various methods.        private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>            WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>        {            { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => MergeTimestamp(message, tokenizer.Next()) },            { Duration.Descriptor.FullName, (parser, message, tokenizer) => MergeDuration(message, tokenizer.Next()) },            { Value.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStructValue(message, tokenizer) },            { ListValue.Descriptor.FullName, (parser, message, tokenizer) =>                parser.MergeRepeatedField(message, message.Descriptor.Fields[ListValue.ValuesFieldNumber], tokenizer) },            { Struct.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStruct(message, tokenizer) },            { Any.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeAny(message, tokenizer) },            { FieldMask.Descriptor.FullName, (parser, message, tokenizer) => MergeFieldMask(message, tokenizer.Next()) },            { Int32Value.Descriptor.FullName, MergeWrapperField },            { Int64Value.Descriptor.FullName, MergeWrapperField },            { UInt32Value.Descriptor.FullName, MergeWrapperField },            { UInt64Value.Descriptor.FullName, MergeWrapperField },            { FloatValue.Descriptor.FullName, MergeWrapperField },            { DoubleValue.Descriptor.FullName, MergeWrapperField },            { BytesValue.Descriptor.FullName, MergeWrapperField },            { StringValue.Descriptor.FullName, MergeWrapperField }        };        // Convenience method to avoid having to repeat the same code multiple times in the above        // dictionary initialization.        private static void MergeWrapperField(JsonParser parser, IMessage message, JsonTokenizer tokenizer)        {            parser.MergeField(message, message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber], tokenizer);        }        /// <summary>        /// Returns a formatter using the default settings.        /// </summary>        public static JsonParser Default { get { return defaultInstance; } }        private readonly Settings settings;        /// <summary>        /// Creates a new formatted with the given settings.        /// </summary>        /// <param name="settings">The settings.</param>        public JsonParser(Settings settings)        {            this.settings = settings;        }        /// <summary>        /// Parses <paramref name="json"/> and merges the information into the given message.        /// </summary>        /// <param name="message">The message to merge the JSON information into.</param>        /// <param name="json">The JSON to parse.</param>        internal void Merge(IMessage message, string json)        {            Merge(message, new StringReader(json));        }        /// <summary>        /// Parses JSON read from <paramref name="jsonReader"/> and merges the information into the given message.        /// </summary>        /// <param name="message">The message to merge the JSON information into.</param>        /// <param name="jsonReader">Reader providing the JSON to parse.</param>        internal void Merge(IMessage message, TextReader jsonReader)        {            var tokenizer = JsonTokenizer.FromTextReader(jsonReader);            Merge(message, tokenizer);            var lastToken = tokenizer.Next();            if (lastToken != JsonToken.EndDocument)            {                throw new InvalidProtocolBufferException("Expected end of JSON after object");            }        }        /// <summary>        /// Merges the given message using data from the given tokenizer. In most cases, the next        /// token should be a "start object" token, but wrapper types and nullity can invalidate        /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream        /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the        /// tokenizer performing that validation - but not every token stream is valid "protobuf JSON".        /// </summary>        private void Merge(IMessage message, JsonTokenizer tokenizer)        {            if (tokenizer.ObjectDepth > settings.RecursionLimit)            {                throw InvalidProtocolBufferException.JsonRecursionLimitExceeded();            }            if (message.Descriptor.IsWellKnownType)            {                Action<JsonParser, IMessage, JsonTokenizer> handler;                if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out handler))                {                    handler(this, message, tokenizer);                    return;                }                // Well-known types with no special handling continue in the normal way.            }            var token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.StartObject)            {                throw new InvalidProtocolBufferException("Expected an object");            }            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();                if (token.Type == JsonToken.TokenType.EndObject)                {                    return;                }                if (token.Type != JsonToken.TokenType.Name)                {                    throw new InvalidOperationException("Unexpected token type " + token.Type);                }                string name = token.StringValue;                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                {                    // TODO: Is this what we want to do? If not, we'll need to skip the value,                    // which may be an object or array. (We might want to put code in the tokenizer                    // to do that.)                    throw new InvalidProtocolBufferException("Unknown field: " + name);                }            }        }        private void MergeField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)        {            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...                if (field.IsMap || field.IsRepeated || !IsGoogleProtobufValueField(field))                {                    field.Accessor.Clear(message);                    return;                }            }            tokenizer.PushBack(token);            if (field.IsMap)            {                MergeMapField(message, field, tokenizer);            }            else if (field.IsRepeated)            {                MergeRepeatedField(message, field, tokenizer);            }            else            {                var value = ParseSingleValue(field, tokenizer);                field.Accessor.SetValue(message, value);            }        }        private void MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)        {            var token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.StartArray)            {                throw new InvalidProtocolBufferException("Repeated field value was not an array. Token type: " + token.Type);            }            IList list = (IList) field.Accessor.GetValue(message);            while (true)            {                token = tokenizer.Next();                if (token.Type == JsonToken.TokenType.EndArray)                {                    return;                }                tokenizer.PushBack(token);                if (token.Type == JsonToken.TokenType.Null)                {                    throw new InvalidProtocolBufferException("Repeated field elements cannot be null");                }                list.Add(ParseSingleValue(field, tokenizer));            }        }        private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)        {            // Map fields are always objects, even if the values are well-known types: ParseSingleValue handles those.            var token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.StartObject)            {                throw new InvalidProtocolBufferException("Expected an object to populate a map");            }            var type = field.MessageType;            var keyField = type.FindFieldByNumber(1);            var valueField = type.FindFieldByNumber(2);            if (keyField == null || valueField == null)            {                throw new InvalidProtocolBufferException("Invalid map field: " + field.FullName);            }            IDictionary dictionary = (IDictionary) field.Accessor.GetValue(message);            while (true)            {                token = tokenizer.Next();                if (token.Type == JsonToken.TokenType.EndObject)                {                    return;                }                object key = ParseMapKey(keyField, token.StringValue);                object value = ParseSingleValue(valueField, tokenizer);                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)            {                // TODO: In order to support dynamic messages, we should really build this up                // dynamically.                if (IsGoogleProtobufValueField(field))                {                    return Value.ForNull();                }                return null;            }            var fieldType = field.FieldType;            if (fieldType == FieldType.Message)            {                // Parse wrapper types as their constituent types.                // TODO: What does this mean for null?                if (field.MessageType.IsWrapperType)                {                    field = field.MessageType.Fields[WrappersReflection.WrapperValueFieldNumber];                    fieldType = field.FieldType;                }                else                {                    // TODO: Merge the current value in message? (Public API currently doesn't make this relevant as we don't expose merging.)                    tokenizer.PushBack(token);                    IMessage subMessage = NewMessageForField(field);                    Merge(subMessage, tokenizer);                    return subMessage;                }            }            switch (token.Type)            {                case JsonToken.TokenType.True:                case JsonToken.TokenType.False:                    if (fieldType == FieldType.Bool)                    {                        return token.Type == JsonToken.TokenType.True;                    }                    // Fall through to "we don't support this type for this case"; could duplicate the behaviour of the default                    // case instead, but this way we'd only need to change one place.                    goto default;                case JsonToken.TokenType.StringValue:                    return ParseSingleStringValue(field, token.StringValue);                // Note: not passing the number value itself here, as we may end up storing the string value in the token too.                case JsonToken.TokenType.Number:                    return ParseSingleNumberValue(field, token);                case JsonToken.TokenType.Null:                    throw new NotImplementedException("Haven't worked out what to do for null yet");                default:                    throw new InvalidProtocolBufferException("Unsupported JSON token type " + token.Type + " for field type " + fieldType);            }        }        /// <summary>        /// Parses <paramref name="json"/> into a new message.        /// </summary>        /// <typeparam name="T">The type of message to create.</typeparam>        /// <param name="json">The JSON to parse.</param>        /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>        /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>        public T Parse<T>(string json) where T : IMessage, new()        {            ProtoPreconditions.CheckNotNull(json, nameof(json));            return Parse<T>(new StringReader(json));        }        /// <summary>        /// Parses JSON read from <paramref name="jsonReader"/> into a new message.        /// </summary>        /// <typeparam name="T">The type of message to create.</typeparam>        /// <param name="jsonReader">Reader providing the JSON to parse.</param>        /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>        /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>        public T Parse<T>(TextReader jsonReader) where T : IMessage, new()        {            ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader));            T message = new T();            Merge(message, jsonReader);            return message;        }        /// <summary>        /// Parses <paramref name="json"/> into a new message.        /// </summary>        /// <param name="json">The JSON to parse.</param>        /// <param name="descriptor">Descriptor of message type to parse.</param>        /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>        /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>        public IMessage Parse(string json, MessageDescriptor descriptor)        {            ProtoPreconditions.CheckNotNull(json, nameof(json));            ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));            return Parse(new StringReader(json), descriptor);        }        /// <summary>        /// Parses JSON read from <paramref name="jsonReader"/> into a new message.        /// </summary>        /// <param name="jsonReader">Reader providing the JSON to parse.</param>        /// <param name="descriptor">Descriptor of message type to parse.</param>        /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>        /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>        public IMessage Parse(TextReader jsonReader, MessageDescriptor descriptor)        {            ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader));            ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor));            IMessage message = descriptor.Parser.CreateTemplate();            Merge(message, jsonReader);            return message;        }        private void MergeStructValue(IMessage message, JsonTokenizer tokenizer)        {            var firstToken = tokenizer.Next();            var fields = message.Descriptor.Fields;            switch (firstToken.Type)            {                case JsonToken.TokenType.Null:                    fields[Value.NullValueFieldNumber].Accessor.SetValue(message, 0);                    return;                case JsonToken.TokenType.StringValue:                    fields[Value.StringValueFieldNumber].Accessor.SetValue(message, firstToken.StringValue);                    return;                case JsonToken.TokenType.Number:                    fields[Value.NumberValueFieldNumber].Accessor.SetValue(message, firstToken.NumberValue);                    return;                case JsonToken.TokenType.False:                case JsonToken.TokenType.True:                    fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, firstToken.Type == JsonToken.TokenType.True);                    return;                case JsonToken.TokenType.StartObject:                    {                        var field = fields[Value.StructValueFieldNumber];                        var structMessage = NewMessageForField(field);                        tokenizer.PushBack(firstToken);                        Merge(structMessage, tokenizer);                        field.Accessor.SetValue(message, structMessage);                        return;                    }                case JsonToken.TokenType.StartArray:                    {                        var field = fields[Value.ListValueFieldNumber];                        var list = NewMessageForField(field);                        tokenizer.PushBack(firstToken);                        Merge(list, tokenizer);                        field.Accessor.SetValue(message, list);                        return;                    }                default:                    throw new InvalidOperationException("Unexpected token type: " + firstToken.Type);            }        }        private void MergeStruct(IMessage message, JsonTokenizer tokenizer)        {            var token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.StartObject)            {                throw new InvalidProtocolBufferException("Expected object value for Struct");            }            tokenizer.PushBack(token);            var field = message.Descriptor.Fields[Struct.FieldsFieldNumber];            MergeMapField(message, field, tokenizer);        }        private void MergeAny(IMessage message, JsonTokenizer tokenizer)        {            // Record the token stream until we see the @type property. At that point, we can take the value, consult            // the type registry for the relevant message, and replay the stream, omitting the @type property.            var tokens = new List<JsonToken>();            var token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.StartObject)            {                throw new InvalidProtocolBufferException("Expected object value for Any");            }            int typeUrlObjectDepth = tokenizer.ObjectDepth;            // The check for the property depth protects us from nested Any values which occur before the type URL            // for *this* Any.            while (token.Type != JsonToken.TokenType.Name ||                token.StringValue != JsonFormatter.AnyTypeUrlField ||                tokenizer.ObjectDepth != typeUrlObjectDepth)            {                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            token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.StringValue)            {                throw new InvalidProtocolBufferException("Expected string value for Any.@type");            }            string typeUrl = token.StringValue;            string typeName = Any.GetTypeName(typeUrl);            MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);            if (descriptor == null)            {                throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");            }            // Now replay the token stream we've already read and anything that remains of the object, just parsing it            // as normal. Our original tokenizer should end up at the end of the object.            var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer);            var body = descriptor.Parser.CreateTemplate();            if (descriptor.IsWellKnownType)            {                MergeWellKnownTypeAnyBody(body, replay);            }            else            {                Merge(body, replay);            }            var data = body.ToByteString();            // Now that we have the message data, we can pack it into an Any (the message received as a parameter).            message.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.SetValue(message, typeUrl);            message.Descriptor.Fields[Any.ValueFieldNumber].Accessor.SetValue(message, data);        }        // Well-known types end up in a property called "value" in the JSON. As there's no longer a @type property        // in the given JSON token stream, we should *only* have tokens of start-object, name("value"), the value        // itself, and then end-object.        private void MergeWellKnownTypeAnyBody(IMessage body, JsonTokenizer tokenizer)        {            var token = tokenizer.Next(); // Definitely start-object; checked in previous method            token = tokenizer.Next();            // TODO: What about an absent Int32Value, for example?            if (token.Type != JsonToken.TokenType.Name || token.StringValue != JsonFormatter.AnyWellKnownTypeValueField)            {                throw new InvalidProtocolBufferException($"Expected '{JsonFormatter.AnyWellKnownTypeValueField}' property for well-known type Any body");            }            Merge(body, tokenizer);            token = tokenizer.Next();            if (token.Type != JsonToken.TokenType.EndObject)            {                throw new InvalidProtocolBufferException($"Expected end-object token after @type/value for well-known type");            }        }        #region Utility methods which don't depend on the state (or settings) of the parser.        private static object ParseMapKey(FieldDescriptor field, string keyText)        {            switch (field.FieldType)            {                case FieldType.Bool:                    if (keyText == "true")                    {                        return true;                    }                    if (keyText == "false")                    {                        return false;                    }                    throw new InvalidProtocolBufferException("Invalid string for bool map key: " + keyText);                case FieldType.String:                    return keyText;                case FieldType.Int32:                case FieldType.SInt32:                case FieldType.SFixed32:                    return ParseNumericString(keyText, int.Parse);                case FieldType.UInt32:                case FieldType.Fixed32:                    return ParseNumericString(keyText, uint.Parse);                case FieldType.Int64:                case FieldType.SInt64:                case FieldType.SFixed64:                    return ParseNumericString(keyText, long.Parse);                case FieldType.UInt64:                case FieldType.Fixed64:                    return ParseNumericString(keyText, ulong.Parse);                default:                    throw new InvalidProtocolBufferException("Invalid field type for map: " + field.FieldType);            }        }        private static object ParseSingleNumberValue(FieldDescriptor field, JsonToken token)        {            double value = token.NumberValue;            checked            {                try                {                    switch (field.FieldType)                    {                        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;                        case FieldType.Float:                            if (double.IsNaN(value))                            {                                return float.NaN;                            }                            if (value > float.MaxValue || value < float.MinValue)                            {                                if (double.IsPositiveInfinity(value))                                {                                    return float.PositiveInfinity;                                }                                if (double.IsNegativeInfinity(value))                                {                                    return float.NegativeInfinity;                                }                                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}");                    }                }                catch (OverflowException)                {                    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)            {                case FieldType.String:                    return text;                case FieldType.Bytes:                    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);                case FieldType.UInt32:                case FieldType.Fixed32:                    return ParseNumericString(text, uint.Parse);                case FieldType.Int64:                case FieldType.SInt64:                case FieldType.SFixed64:                    return ParseNumericString(text, long.Parse);                case FieldType.UInt64:                case FieldType.Fixed64:                    return ParseNumericString(text, ulong.Parse);                case FieldType.Double:                    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);                    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}");                    }                    // 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}");            }        }        /// <summary>        /// Creates a new instance of the message type for the given field.        /// </summary>        private static IMessage NewMessageForField(FieldDescriptor field)        {            return field.MessageType.Parser.CreateTemplate();        }        private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser)        {            // Can't prohibit this with NumberStyles.            if (text.StartsWith("+"))            {                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}");                }            }            else if (text.StartsWith("-0") && text.Length > 2)            {                if (text[2] >= '0' && text[2] <= '9')                {                    throw new InvalidProtocolBufferException($"Invalid numeric value: {text}");                }            }            try            {                return parser(text, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture);            }            catch (FormatException)            {                throw new InvalidProtocolBufferException($"Invalid numeric value for type: {text}");            }            catch (OverflowException)            {                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}");            }        }        private static void MergeTimestamp(IMessage message, JsonToken token)        {            if (token.Type != JsonToken.TokenType.StringValue)            {                throw new InvalidProtocolBufferException("Expected string value for Timestamp");            }            var match = TimestampRegex.Match(token.StringValue);            if (!match.Success)            {                throw new InvalidProtocolBufferException($"Invalid Timestamp value: {token.StringValue}");            }            var dateTime = match.Groups["datetime"].Value;            var subseconds = match.Groups["subseconds"].Value;            var offset = match.Groups["offset"].Value;            try            {                DateTime parsed = DateTime.ParseExact(                    dateTime,                    "yyyy-MM-dd'T'HH:mm:ss",                    CultureInfo.InvariantCulture,                    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);                // TODO: It would be nice not to have to create all these objects... easy to optimize later though.                Timestamp timestamp = Timestamp.FromDateTime(parsed);                int nanosToAdd = 0;                if (subseconds != "")                {                    // This should always work, as we've got 1-9 digits.                    int parsedFraction = int.Parse(subseconds.Substring(1), CultureInfo.InvariantCulture);                    nanosToAdd = parsedFraction * SubsecondScalingFactors[subseconds.Length];                }                int secondsToAdd = 0;                if (offset != "Z")                {                    // This is the amount we need to *subtract* from the local time to get to UTC - hence - => +1 and vice versa.                    int sign = offset[0] == '-' ? 1 : -1;                    int hours = int.Parse(offset.Substring(1, 2), CultureInfo.InvariantCulture);                    int minutes = int.Parse(offset.Substring(4, 2));                    int totalMinutes = hours * 60 + minutes;                    if (totalMinutes > 18 * 60)                    {                        throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);                    }                    if (totalMinutes == 0 && sign == 1)                    {                        // This is an offset of -00:00, which means "unknown local offset". It makes no sense for a timestamp.                        throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);                    }                    // We need to *subtract* the offset from local time to get UTC.                    secondsToAdd = sign * totalMinutes * 60;                }                // Ensure we've got the right signs. Currently unnecessary, but easy to do.                if (secondsToAdd < 0 && nanosToAdd > 0)                {                    secondsToAdd++;                    nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond;                }                if (secondsToAdd != 0 || nanosToAdd != 0)                {                    timestamp += new Duration { Nanos = nanosToAdd, Seconds = secondsToAdd };                    // The resulting timestamp after offset change would be out of our expected range. Currently the Timestamp message doesn't validate this                    // anywhere, but we shouldn't parse it.                    if (timestamp.Seconds < Timestamp.UnixSecondsAtBclMinValue || timestamp.Seconds > Timestamp.UnixSecondsAtBclMaxValue)                    {                        throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);                    }                }                message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, timestamp.Seconds);                message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, timestamp.Nanos);            }            catch (FormatException)            {                throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);            }        }        private static void MergeDuration(IMessage message, JsonToken token)        {            if (token.Type != JsonToken.TokenType.StringValue)            {                throw new InvalidProtocolBufferException("Expected string value for Duration");            }            var match = DurationRegex.Match(token.StringValue);            if (!match.Success)            {                throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);            }            var sign = match.Groups["sign"].Value;            var secondsText = match.Groups["int"].Value;            // Prohibit leading insignficant zeroes            if (secondsText[0] == '0' && secondsText.Length > 1)            {                throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);            }            var subseconds = match.Groups["subseconds"].Value;            var multiplier = sign == "-" ? -1 : 1;            try            {                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] * multiplier;                }                if (!Duration.IsNormalized(seconds, nanos))                {                    throw new InvalidProtocolBufferException($"Invalid Duration value: {token.StringValue}");                }                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}");            }        }        private static void MergeFieldMask(IMessage message, JsonToken token)        {            if (token.Type != JsonToken.TokenType.StringValue)            {                throw new InvalidProtocolBufferException("Expected string value for FieldMask");            }            // TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"?            string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators, StringSplitOptions.RemoveEmptyEntries);            IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message);            foreach (var path in jsonPaths)            {                messagePaths.Add(ToSnakeCase(path));            }        }                // Ported from src/google/protobuf/util/internal/utility.cc        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;            for (int i = 0; i < text.Length; i++)            {                char c = text[i];                if (c >= 'A' && c <= 'Z') // ascii_isupper                {                    // Consider when the current character B is capitalized:                    // 1) At beginning of input:   "B..." => "b..."                    //    (e.g. "Biscuit" => "biscuit")                    // 2) Following a lowercase:   "...aB..." => "...a_b..."                    //    (e.g. "gBike" => "g_bike")                    // 3) At the end of input:     "...AB" => "...ab"                    //    (e.g. "GoogleLAB" => "google_lab")                    // 4) Followed by a lowercase: "...ABc..." => "...a_bc..."                    //    (e.g. "GBike" => "g_bike")                    if (wasNotUnderscore &&               //            case 1 out                        (wasNotCap ||                     // case 2 in, case 3 out                         (i + 1 < text.Length &&         //            case 3 out                          (text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1])                    {  // case 4 in                       // We add an underscore for case 2 and case 4.                        builder.Append('_');                    }                    // ascii_tolower, but we already know that c *is* an upper case ASCII character...                    builder.Append((char) (c + 'a' - 'A'));                    wasNotUnderscore = true;                    wasNotCap = false;                }                else                {                    builder.Append(c);                    if (c == '_')                    {                        throw new InvalidProtocolBufferException($"Invalid field mask: {text}");                    }                    wasNotUnderscore = true;                    wasNotCap = true;                }            }            return builder.ToString();        }        #endregion        /// <summary>        /// Settings controlling JSON parsing.        /// </summary>        public sealed class Settings        {            /// <summary>            /// Default settings, as used by <see cref="JsonParser.Default"/>. This has the same default            /// recursion limit as <see cref="CodedInputStream"/>, and an empty type registry.            /// </summary>            public static Settings Default { get; }            // Workaround for the Mono compiler complaining about XML comments not being on            // valid language elements.            static Settings()            {                Default = new Settings(CodedInputStream.DefaultRecursionLimit);            }            /// <summary>            /// The maximum depth of messages to parse. Note that this limit only applies to parsing            /// messages, not collections - so a message within a collection within a message only counts as            /// depth 2, not 3.            /// </summary>            public int RecursionLimit { get; }            /// <summary>            /// The type registry used to parse <see cref="Any"/> messages.            /// </summary>            public TypeRegistry TypeRegistry { get; }            /// <summary>            /// Creates a new <see cref="Settings"/> object with the specified recursion limit.            /// </summary>            /// <param name="recursionLimit">The maximum depth of messages to parse</param>            public Settings(int recursionLimit) : this(recursionLimit, TypeRegistry.Empty)            {            }            /// <summary>            /// Creates a new <see cref="Settings"/> object with the specified recursion limit and type registry.            /// </summary>            /// <param name="recursionLimit">The maximum depth of messages to parse</param>            /// <param name="typeRegistry">The type registry used to parse <see cref="Any"/> messages</param>            public Settings(int recursionLimit, TypeRegistry typeRegistry)            {                RecursionLimit = recursionLimit;                TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));            }        }    }}
 |