OLD | NEW |
(Empty) | |
| 1 #region Copyright notice and license |
| 2 // Protocol Buffers - Google's data interchange format |
| 3 // Copyright 2015 Google Inc. All rights reserved. |
| 4 // https://developers.google.com/protocol-buffers/ |
| 5 // |
| 6 // Redistribution and use in source and binary forms, with or without |
| 7 // modification, are permitted provided that the following conditions are |
| 8 // met: |
| 9 // |
| 10 // * Redistributions of source code must retain the above copyright |
| 11 // notice, this list of conditions and the following disclaimer. |
| 12 // * Redistributions in binary form must reproduce the above |
| 13 // copyright notice, this list of conditions and the following disclaimer |
| 14 // in the documentation and/or other materials provided with the |
| 15 // distribution. |
| 16 // * Neither the name of Google Inc. nor the names of its |
| 17 // contributors may be used to endorse or promote products derived from |
| 18 // this software without specific prior written permission. |
| 19 // |
| 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 #endregion |
| 32 |
| 33 using Google.Protobuf.Reflection; |
| 34 using Google.Protobuf.WellKnownTypes; |
| 35 using System; |
| 36 using System.Collections; |
| 37 using System.Collections.Generic; |
| 38 using System.Globalization; |
| 39 using System.IO; |
| 40 using System.Text; |
| 41 using System.Text.RegularExpressions; |
| 42 |
| 43 namespace Google.Protobuf |
| 44 { |
| 45 /// <summary> |
| 46 /// Reflection-based converter from JSON to messages. |
| 47 /// </summary> |
| 48 /// <remarks> |
| 49 /// <para> |
| 50 /// Instances of this class are thread-safe, with no mutable state. |
| 51 /// </para> |
| 52 /// <para> |
| 53 /// This is a simple start to get JSON parsing working. As it's reflection-b
ased, |
| 54 /// it's not as quick as baking calls into generated messages - but is a sim
pler implementation. |
| 55 /// (This code is generally not heavily optimized.) |
| 56 /// </para> |
| 57 /// </remarks> |
| 58 public sealed class JsonParser |
| 59 { |
| 60 // Note: using 0-9 instead of \d to ensure no non-ASCII digits. |
| 61 // This regex isn't a complete validator, but will remove *most* invalid
input. We rely on parsing to do the rest. |
| 62 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.Comp
iledRegexWhereAvailable); |
| 63 private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?
<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledR
egexWhereAvailable); |
| 64 private static readonly int[] SubsecondScalingFactors = { 0, 100000000,
100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 }; |
| 65 private static readonly char[] FieldMaskPathSeparators = new[] { ',' }; |
| 66 |
| 67 private static readonly JsonParser defaultInstance = new JsonParser(Sett
ings.Default); |
| 68 |
| 69 // TODO: Consider introducing a class containing parse state of the pars
er, tokenizer and depth. That would simplify these handlers |
| 70 // and the signatures of various methods. |
| 71 private static readonly Dictionary<string, Action<JsonParser, IMessage,
JsonTokenizer>> |
| 72 WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IM
essage, JsonTokenizer>> |
| 73 { |
| 74 { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => Mer
geTimestamp(message, tokenizer.Next()) }, |
| 75 { Duration.Descriptor.FullName, (parser, message, tokenizer) => Merg
eDuration(message, tokenizer.Next()) }, |
| 76 { Value.Descriptor.FullName, (parser, message, tokenizer) => parser.
MergeStructValue(message, tokenizer) }, |
| 77 { ListValue.Descriptor.FullName, (parser, message, tokenizer) => |
| 78 parser.MergeRepeatedField(message, message.Descriptor.Fields[Lis
tValue.ValuesFieldNumber], tokenizer) }, |
| 79 { Struct.Descriptor.FullName, (parser, message, tokenizer) => parser
.MergeStruct(message, tokenizer) }, |
| 80 { Any.Descriptor.FullName, (parser, message, tokenizer) => parser.Me
rgeAny(message, tokenizer) }, |
| 81 { FieldMask.Descriptor.FullName, (parser, message, tokenizer) => Mer
geFieldMask(message, tokenizer.Next()) }, |
| 82 { Int32Value.Descriptor.FullName, MergeWrapperField }, |
| 83 { Int64Value.Descriptor.FullName, MergeWrapperField }, |
| 84 { UInt32Value.Descriptor.FullName, MergeWrapperField }, |
| 85 { UInt64Value.Descriptor.FullName, MergeWrapperField }, |
| 86 { FloatValue.Descriptor.FullName, MergeWrapperField }, |
| 87 { DoubleValue.Descriptor.FullName, MergeWrapperField }, |
| 88 { BytesValue.Descriptor.FullName, MergeWrapperField }, |
| 89 { StringValue.Descriptor.FullName, MergeWrapperField } |
| 90 }; |
| 91 |
| 92 // Convenience method to avoid having to repeat the same code multiple t
imes in the above |
| 93 // dictionary initialization. |
| 94 private static void MergeWrapperField(JsonParser parser, IMessage messag
e, JsonTokenizer tokenizer) |
| 95 { |
| 96 parser.MergeField(message, message.Descriptor.Fields[WrappersReflect
ion.WrapperValueFieldNumber], tokenizer); |
| 97 } |
| 98 |
| 99 /// <summary> |
| 100 /// Returns a formatter using the default settings. |
| 101 /// </summary> |
| 102 public static JsonParser Default { get { return defaultInstance; } } |
| 103 |
| 104 private readonly Settings settings; |
| 105 |
| 106 /// <summary> |
| 107 /// Creates a new formatted with the given settings. |
| 108 /// </summary> |
| 109 /// <param name="settings">The settings.</param> |
| 110 public JsonParser(Settings settings) |
| 111 { |
| 112 this.settings = settings; |
| 113 } |
| 114 |
| 115 /// <summary> |
| 116 /// Parses <paramref name="json"/> and merges the information into the g
iven message. |
| 117 /// </summary> |
| 118 /// <param name="message">The message to merge the JSON information into
.</param> |
| 119 /// <param name="json">The JSON to parse.</param> |
| 120 internal void Merge(IMessage message, string json) |
| 121 { |
| 122 Merge(message, new StringReader(json)); |
| 123 } |
| 124 |
| 125 /// <summary> |
| 126 /// Parses JSON read from <paramref name="jsonReader"/> and merges the i
nformation into the given message. |
| 127 /// </summary> |
| 128 /// <param name="message">The message to merge the JSON information into
.</param> |
| 129 /// <param name="jsonReader">Reader providing the JSON to parse.</param> |
| 130 internal void Merge(IMessage message, TextReader jsonReader) |
| 131 { |
| 132 var tokenizer = JsonTokenizer.FromTextReader(jsonReader); |
| 133 Merge(message, tokenizer); |
| 134 var lastToken = tokenizer.Next(); |
| 135 if (lastToken != JsonToken.EndDocument) |
| 136 { |
| 137 throw new InvalidProtocolBufferException("Expected end of JSON a
fter object"); |
| 138 } |
| 139 } |
| 140 |
| 141 /// <summary> |
| 142 /// Merges the given message using data from the given tokenizer. In mos
t cases, the next |
| 143 /// token should be a "start object" token, but wrapper types and nullit
y can invalidate |
| 144 /// that assumption. This is implemented as an LL(1) recursive descent p
arser over the stream |
| 145 /// of tokens provided by the tokenizer. This token stream is assumed to
be valid JSON, with the |
| 146 /// tokenizer performing that validation - but not every token stream is
valid "protobuf JSON". |
| 147 /// </summary> |
| 148 private void Merge(IMessage message, JsonTokenizer tokenizer) |
| 149 { |
| 150 if (tokenizer.ObjectDepth > settings.RecursionLimit) |
| 151 { |
| 152 throw InvalidProtocolBufferException.JsonRecursionLimitExceeded(
); |
| 153 } |
| 154 if (message.Descriptor.IsWellKnownType) |
| 155 { |
| 156 Action<JsonParser, IMessage, JsonTokenizer> handler; |
| 157 if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullNam
e, out handler)) |
| 158 { |
| 159 handler(this, message, tokenizer); |
| 160 return; |
| 161 } |
| 162 // Well-known types with no special handling continue in the nor
mal way. |
| 163 } |
| 164 var token = tokenizer.Next(); |
| 165 if (token.Type != JsonToken.TokenType.StartObject) |
| 166 { |
| 167 throw new InvalidProtocolBufferException("Expected an object"); |
| 168 } |
| 169 var descriptor = message.Descriptor; |
| 170 var jsonFieldMap = descriptor.Fields.ByJsonName(); |
| 171 while (true) |
| 172 { |
| 173 token = tokenizer.Next(); |
| 174 if (token.Type == JsonToken.TokenType.EndObject) |
| 175 { |
| 176 return; |
| 177 } |
| 178 if (token.Type != JsonToken.TokenType.Name) |
| 179 { |
| 180 throw new InvalidOperationException("Unexpected token type "
+ token.Type); |
| 181 } |
| 182 string name = token.StringValue; |
| 183 FieldDescriptor field; |
| 184 if (jsonFieldMap.TryGetValue(name, out field)) |
| 185 { |
| 186 MergeField(message, field, tokenizer); |
| 187 } |
| 188 else |
| 189 { |
| 190 // TODO: Is this what we want to do? If not, we'll need to s
kip the value, |
| 191 // which may be an object or array. (We might want to put co
de in the tokenizer |
| 192 // to do that.) |
| 193 throw new InvalidProtocolBufferException("Unknown field: " +
name); |
| 194 } |
| 195 } |
| 196 } |
| 197 |
| 198 private void MergeField(IMessage message, FieldDescriptor field, JsonTok
enizer tokenizer) |
| 199 { |
| 200 var token = tokenizer.Next(); |
| 201 if (token.Type == JsonToken.TokenType.Null) |
| 202 { |
| 203 // Note: different from Java API, which just ignores it. |
| 204 // TODO: Bring it more in line? Discuss... |
| 205 field.Accessor.Clear(message); |
| 206 return; |
| 207 } |
| 208 tokenizer.PushBack(token); |
| 209 |
| 210 if (field.IsMap) |
| 211 { |
| 212 MergeMapField(message, field, tokenizer); |
| 213 } |
| 214 else if (field.IsRepeated) |
| 215 { |
| 216 MergeRepeatedField(message, field, tokenizer); |
| 217 } |
| 218 else |
| 219 { |
| 220 var value = ParseSingleValue(field, tokenizer); |
| 221 field.Accessor.SetValue(message, value); |
| 222 } |
| 223 } |
| 224 |
| 225 private void MergeRepeatedField(IMessage message, FieldDescriptor field,
JsonTokenizer tokenizer) |
| 226 { |
| 227 var token = tokenizer.Next(); |
| 228 if (token.Type != JsonToken.TokenType.StartArray) |
| 229 { |
| 230 throw new InvalidProtocolBufferException("Repeated field value w
as not an array. Token type: " + token.Type); |
| 231 } |
| 232 |
| 233 IList list = (IList) field.Accessor.GetValue(message); |
| 234 while (true) |
| 235 { |
| 236 token = tokenizer.Next(); |
| 237 if (token.Type == JsonToken.TokenType.EndArray) |
| 238 { |
| 239 return; |
| 240 } |
| 241 tokenizer.PushBack(token); |
| 242 list.Add(ParseSingleValue(field, tokenizer)); |
| 243 } |
| 244 } |
| 245 |
| 246 private void MergeMapField(IMessage message, FieldDescriptor field, Json
Tokenizer tokenizer) |
| 247 { |
| 248 // Map fields are always objects, even if the values are well-known
types: ParseSingleValue handles those. |
| 249 var token = tokenizer.Next(); |
| 250 if (token.Type != JsonToken.TokenType.StartObject) |
| 251 { |
| 252 throw new InvalidProtocolBufferException("Expected an object to
populate a map"); |
| 253 } |
| 254 |
| 255 var type = field.MessageType; |
| 256 var keyField = type.FindFieldByNumber(1); |
| 257 var valueField = type.FindFieldByNumber(2); |
| 258 if (keyField == null || valueField == null) |
| 259 { |
| 260 throw new InvalidProtocolBufferException("Invalid map field: " +
field.FullName); |
| 261 } |
| 262 IDictionary dictionary = (IDictionary) field.Accessor.GetValue(messa
ge); |
| 263 |
| 264 while (true) |
| 265 { |
| 266 token = tokenizer.Next(); |
| 267 if (token.Type == JsonToken.TokenType.EndObject) |
| 268 { |
| 269 return; |
| 270 } |
| 271 object key = ParseMapKey(keyField, token.StringValue); |
| 272 object value = ParseSingleValue(valueField, tokenizer); |
| 273 // TODO: Null handling |
| 274 dictionary[key] = value; |
| 275 } |
| 276 } |
| 277 |
| 278 private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tok
enizer) |
| 279 { |
| 280 var token = tokenizer.Next(); |
| 281 if (token.Type == JsonToken.TokenType.Null) |
| 282 { |
| 283 if (field.FieldType == FieldType.Message && field.MessageType.Fu
llName == Value.Descriptor.FullName) |
| 284 { |
| 285 return new Value { NullValue = NullValue.NULL_VALUE }; |
| 286 } |
| 287 return null; |
| 288 } |
| 289 |
| 290 var fieldType = field.FieldType; |
| 291 if (fieldType == FieldType.Message) |
| 292 { |
| 293 // Parse wrapper types as their constituent types. |
| 294 // TODO: What does this mean for null? |
| 295 if (field.MessageType.IsWrapperType) |
| 296 { |
| 297 field = field.MessageType.Fields[WrappersReflection.WrapperV
alueFieldNumber]; |
| 298 fieldType = field.FieldType; |
| 299 } |
| 300 else |
| 301 { |
| 302 // TODO: Merge the current value in message? (Public API cur
rently doesn't make this relevant as we don't expose merging.) |
| 303 tokenizer.PushBack(token); |
| 304 IMessage subMessage = NewMessageForField(field); |
| 305 Merge(subMessage, tokenizer); |
| 306 return subMessage; |
| 307 } |
| 308 } |
| 309 |
| 310 switch (token.Type) |
| 311 { |
| 312 case JsonToken.TokenType.True: |
| 313 case JsonToken.TokenType.False: |
| 314 if (fieldType == FieldType.Bool) |
| 315 { |
| 316 return token.Type == JsonToken.TokenType.True; |
| 317 } |
| 318 // Fall through to "we don't support this type for this case
"; could duplicate the behaviour of the default |
| 319 // case instead, but this way we'd only need to change one p
lace. |
| 320 goto default; |
| 321 case JsonToken.TokenType.StringValue: |
| 322 return ParseSingleStringValue(field, token.StringValue); |
| 323 // Note: not passing the number value itself here, as we may end
up storing the string value in the token too. |
| 324 case JsonToken.TokenType.Number: |
| 325 return ParseSingleNumberValue(field, token); |
| 326 case JsonToken.TokenType.Null: |
| 327 throw new NotImplementedException("Haven't worked out what t
o do for null yet"); |
| 328 default: |
| 329 throw new InvalidProtocolBufferException("Unsupported JSON t
oken type " + token.Type + " for field type " + fieldType); |
| 330 } |
| 331 } |
| 332 |
| 333 /// <summary> |
| 334 /// Parses <paramref name="json"/> into a new message. |
| 335 /// </summary> |
| 336 /// <typeparam name="T">The type of message to create.</typeparam> |
| 337 /// <param name="json">The JSON to parse.</param> |
| 338 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> |
| 339 /// <exception cref="InvalidProtocolBufferException">The JSON does not r
epresent a Protocol Buffers message correctly</exception> |
| 340 public T Parse<T>(string json) where T : IMessage, new() |
| 341 { |
| 342 Preconditions.CheckNotNull(json, nameof(json)); |
| 343 return Parse<T>(new StringReader(json)); |
| 344 } |
| 345 |
| 346 /// <summary> |
| 347 /// Parses JSON read from <paramref name="jsonReader"/> into a new messa
ge. |
| 348 /// </summary> |
| 349 /// <typeparam name="T">The type of message to create.</typeparam> |
| 350 /// <param name="jsonReader">Reader providing the JSON to parse.</param> |
| 351 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> |
| 352 /// <exception cref="InvalidProtocolBufferException">The JSON does not r
epresent a Protocol Buffers message correctly</exception> |
| 353 public T Parse<T>(TextReader jsonReader) where T : IMessage, new() |
| 354 { |
| 355 Preconditions.CheckNotNull(jsonReader, nameof(jsonReader)); |
| 356 T message = new T(); |
| 357 Merge(message, jsonReader); |
| 358 return message; |
| 359 } |
| 360 |
| 361 /// <summary> |
| 362 /// Parses <paramref name="json"/> into a new message. |
| 363 /// </summary> |
| 364 /// <param name="json">The JSON to parse.</param> |
| 365 /// <param name="descriptor">Descriptor of message type to parse.</param
> |
| 366 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> |
| 367 /// <exception cref="InvalidProtocolBufferException">The JSON does not r
epresent a Protocol Buffers message correctly</exception> |
| 368 public IMessage Parse(string json, MessageDescriptor descriptor) |
| 369 { |
| 370 Preconditions.CheckNotNull(json, nameof(json)); |
| 371 Preconditions.CheckNotNull(descriptor, nameof(descriptor)); |
| 372 return Parse(new StringReader(json), descriptor); |
| 373 } |
| 374 |
| 375 /// <summary> |
| 376 /// Parses JSON read from <paramref name="jsonReader"/> into a new messa
ge. |
| 377 /// </summary> |
| 378 /// <param name="jsonReader">Reader providing the JSON to parse.</param> |
| 379 /// <param name="descriptor">Descriptor of message type to parse.</param
> |
| 380 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> |
| 381 /// <exception cref="InvalidProtocolBufferException">The JSON does not r
epresent a Protocol Buffers message correctly</exception> |
| 382 public IMessage Parse(TextReader jsonReader, MessageDescriptor descripto
r) |
| 383 { |
| 384 Preconditions.CheckNotNull(jsonReader, nameof(jsonReader)); |
| 385 Preconditions.CheckNotNull(descriptor, nameof(descriptor)); |
| 386 IMessage message = descriptor.Parser.CreateTemplate(); |
| 387 Merge(message, jsonReader); |
| 388 return message; |
| 389 } |
| 390 |
| 391 private void MergeStructValue(IMessage message, JsonTokenizer tokenizer) |
| 392 { |
| 393 var firstToken = tokenizer.Next(); |
| 394 var fields = message.Descriptor.Fields; |
| 395 switch (firstToken.Type) |
| 396 { |
| 397 case JsonToken.TokenType.Null: |
| 398 fields[Value.NullValueFieldNumber].Accessor.SetValue(message
, 0); |
| 399 return; |
| 400 case JsonToken.TokenType.StringValue: |
| 401 fields[Value.StringValueFieldNumber].Accessor.SetValue(messa
ge, firstToken.StringValue); |
| 402 return; |
| 403 case JsonToken.TokenType.Number: |
| 404 fields[Value.NumberValueFieldNumber].Accessor.SetValue(messa
ge, firstToken.NumberValue); |
| 405 return; |
| 406 case JsonToken.TokenType.False: |
| 407 case JsonToken.TokenType.True: |
| 408 fields[Value.BoolValueFieldNumber].Accessor.SetValue(message
, firstToken.Type == JsonToken.TokenType.True); |
| 409 return; |
| 410 case JsonToken.TokenType.StartObject: |
| 411 { |
| 412 var field = fields[Value.StructValueFieldNumber]; |
| 413 var structMessage = NewMessageForField(field); |
| 414 tokenizer.PushBack(firstToken); |
| 415 Merge(structMessage, tokenizer); |
| 416 field.Accessor.SetValue(message, structMessage); |
| 417 return; |
| 418 } |
| 419 case JsonToken.TokenType.StartArray: |
| 420 { |
| 421 var field = fields[Value.ListValueFieldNumber]; |
| 422 var list = NewMessageForField(field); |
| 423 tokenizer.PushBack(firstToken); |
| 424 Merge(list, tokenizer); |
| 425 field.Accessor.SetValue(message, list); |
| 426 return; |
| 427 } |
| 428 default: |
| 429 throw new InvalidOperationException("Unexpected token type:
" + firstToken.Type); |
| 430 } |
| 431 } |
| 432 |
| 433 private void MergeStruct(IMessage message, JsonTokenizer tokenizer) |
| 434 { |
| 435 var token = tokenizer.Next(); |
| 436 if (token.Type != JsonToken.TokenType.StartObject) |
| 437 { |
| 438 throw new InvalidProtocolBufferException("Expected object value
for Struct"); |
| 439 } |
| 440 tokenizer.PushBack(token); |
| 441 |
| 442 var field = message.Descriptor.Fields[Struct.FieldsFieldNumber]; |
| 443 MergeMapField(message, field, tokenizer); |
| 444 } |
| 445 |
| 446 private void MergeAny(IMessage message, JsonTokenizer tokenizer) |
| 447 { |
| 448 // Record the token stream until we see the @type property. At that
point, we can take the value, consult |
| 449 // the type registry for the relevant message, and replay the stream
, omitting the @type property. |
| 450 var tokens = new List<JsonToken>(); |
| 451 |
| 452 var token = tokenizer.Next(); |
| 453 if (token.Type != JsonToken.TokenType.StartObject) |
| 454 { |
| 455 throw new InvalidProtocolBufferException("Expected object value
for Any"); |
| 456 } |
| 457 int typeUrlObjectDepth = tokenizer.ObjectDepth; |
| 458 |
| 459 // The check for the property depth protects us from nested Any valu
es which occur before the type URL |
| 460 // for *this* Any. |
| 461 while (token.Type != JsonToken.TokenType.Name || |
| 462 token.StringValue != JsonFormatter.AnyTypeUrlField || |
| 463 tokenizer.ObjectDepth != typeUrlObjectDepth) |
| 464 { |
| 465 tokens.Add(token); |
| 466 token = tokenizer.Next(); |
| 467 } |
| 468 |
| 469 // Don't add the @type property or its value to the recorded token l
ist |
| 470 token = tokenizer.Next(); |
| 471 if (token.Type != JsonToken.TokenType.StringValue) |
| 472 { |
| 473 throw new InvalidProtocolBufferException("Expected string value
for Any.@type"); |
| 474 } |
| 475 string typeUrl = token.StringValue; |
| 476 string typeName = JsonFormatter.GetTypeName(typeUrl); |
| 477 |
| 478 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName); |
| 479 if (descriptor == null) |
| 480 { |
| 481 throw new InvalidOperationException($"Type registry has no descr
iptor for type name '{typeName}'"); |
| 482 } |
| 483 |
| 484 // Now replay the token stream we've already read and anything that
remains of the object, just parsing it |
| 485 // as normal. Our original tokenizer should end up at the end of the
object. |
| 486 var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer); |
| 487 var body = descriptor.Parser.CreateTemplate(); |
| 488 if (descriptor.IsWellKnownType) |
| 489 { |
| 490 MergeWellKnownTypeAnyBody(body, replay); |
| 491 } |
| 492 else |
| 493 { |
| 494 Merge(body, replay); |
| 495 } |
| 496 var data = body.ToByteString(); |
| 497 |
| 498 // Now that we have the message data, we can pack it into an Any (th
e message received as a parameter). |
| 499 message.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.SetValue(
message, typeUrl); |
| 500 message.Descriptor.Fields[Any.ValueFieldNumber].Accessor.SetValue(me
ssage, data); |
| 501 } |
| 502 |
| 503 // Well-known types end up in a property called "value" in the JSON. As
there's no longer a @type property |
| 504 // in the given JSON token stream, we should *only* have tokens of start
-object, name("value"), the value |
| 505 // itself, and then end-object. |
| 506 private void MergeWellKnownTypeAnyBody(IMessage body, JsonTokenizer toke
nizer) |
| 507 { |
| 508 var token = tokenizer.Next(); // Definitely start-object; checked in
previous method |
| 509 token = tokenizer.Next(); |
| 510 // TODO: What about an absent Int32Value, for example? |
| 511 if (token.Type != JsonToken.TokenType.Name || token.StringValue != J
sonFormatter.AnyWellKnownTypeValueField) |
| 512 { |
| 513 throw new InvalidProtocolBufferException($"Expected '{JsonFormat
ter.AnyWellKnownTypeValueField}' property for well-known type Any body"); |
| 514 } |
| 515 Merge(body, tokenizer); |
| 516 token = tokenizer.Next(); |
| 517 if (token.Type != JsonToken.TokenType.EndObject) |
| 518 { |
| 519 throw new InvalidProtocolBufferException($"Expected end-object t
oken after @type/value for well-known type"); |
| 520 } |
| 521 } |
| 522 |
| 523 #region Utility methods which don't depend on the state (or settings) of
the parser. |
| 524 private static object ParseMapKey(FieldDescriptor field, string keyText) |
| 525 { |
| 526 switch (field.FieldType) |
| 527 { |
| 528 case FieldType.Bool: |
| 529 if (keyText == "true") |
| 530 { |
| 531 return true; |
| 532 } |
| 533 if (keyText == "false") |
| 534 { |
| 535 return false; |
| 536 } |
| 537 throw new InvalidProtocolBufferException("Invalid string for
bool map key: " + keyText); |
| 538 case FieldType.String: |
| 539 return keyText; |
| 540 case FieldType.Int32: |
| 541 case FieldType.SInt32: |
| 542 case FieldType.SFixed32: |
| 543 return ParseNumericString(keyText, int.Parse, false); |
| 544 case FieldType.UInt32: |
| 545 case FieldType.Fixed32: |
| 546 return ParseNumericString(keyText, uint.Parse, false); |
| 547 case FieldType.Int64: |
| 548 case FieldType.SInt64: |
| 549 case FieldType.SFixed64: |
| 550 return ParseNumericString(keyText, long.Parse, false); |
| 551 case FieldType.UInt64: |
| 552 case FieldType.Fixed64: |
| 553 return ParseNumericString(keyText, ulong.Parse, false); |
| 554 default: |
| 555 throw new InvalidProtocolBufferException("Invalid field type
for map: " + field.FieldType); |
| 556 } |
| 557 } |
| 558 |
| 559 private static object ParseSingleNumberValue(FieldDescriptor field, Json
Token token) |
| 560 { |
| 561 double value = token.NumberValue; |
| 562 checked |
| 563 { |
| 564 // TODO: Validate that it's actually an integer, possibly in ter
ms of the textual representation? |
| 565 try |
| 566 { |
| 567 switch (field.FieldType) |
| 568 { |
| 569 case FieldType.Int32: |
| 570 case FieldType.SInt32: |
| 571 case FieldType.SFixed32: |
| 572 return (int) value; |
| 573 case FieldType.UInt32: |
| 574 case FieldType.Fixed32: |
| 575 return (uint) value; |
| 576 case FieldType.Int64: |
| 577 case FieldType.SInt64: |
| 578 case FieldType.SFixed64: |
| 579 return (long) value; |
| 580 case FieldType.UInt64: |
| 581 case FieldType.Fixed64: |
| 582 return (ulong) value; |
| 583 case FieldType.Double: |
| 584 return value; |
| 585 case FieldType.Float: |
| 586 if (double.IsNaN(value)) |
| 587 { |
| 588 return float.NaN; |
| 589 } |
| 590 if (value > float.MaxValue || value < float.MinValue
) |
| 591 { |
| 592 if (double.IsPositiveInfinity(value)) |
| 593 { |
| 594 return float.PositiveInfinity; |
| 595 } |
| 596 if (double.IsNegativeInfinity(value)) |
| 597 { |
| 598 return float.NegativeInfinity; |
| 599 } |
| 600 throw new InvalidProtocolBufferException("Value
out of range: " + value); |
| 601 } |
| 602 return (float) value; |
| 603 default: |
| 604 throw new InvalidProtocolBufferException("Unsupporte
d conversion from JSON number for field type " + field.FieldType); |
| 605 } |
| 606 } |
| 607 catch (OverflowException) |
| 608 { |
| 609 throw new InvalidProtocolBufferException("Value out of range
: " + value); |
| 610 } |
| 611 } |
| 612 } |
| 613 |
| 614 private static object ParseSingleStringValue(FieldDescriptor field, stri
ng text) |
| 615 { |
| 616 switch (field.FieldType) |
| 617 { |
| 618 case FieldType.String: |
| 619 return text; |
| 620 case FieldType.Bytes: |
| 621 return ByteString.FromBase64(text); |
| 622 case FieldType.Int32: |
| 623 case FieldType.SInt32: |
| 624 case FieldType.SFixed32: |
| 625 return ParseNumericString(text, int.Parse, false); |
| 626 case FieldType.UInt32: |
| 627 case FieldType.Fixed32: |
| 628 return ParseNumericString(text, uint.Parse, false); |
| 629 case FieldType.Int64: |
| 630 case FieldType.SInt64: |
| 631 case FieldType.SFixed64: |
| 632 return ParseNumericString(text, long.Parse, false); |
| 633 case FieldType.UInt64: |
| 634 case FieldType.Fixed64: |
| 635 return ParseNumericString(text, ulong.Parse, false); |
| 636 case FieldType.Double: |
| 637 double d = ParseNumericString(text, double.Parse, true); |
| 638 // double.Parse can return +/- infinity on Mono for non-infi
nite values which are out of range for double. |
| 639 if (double.IsInfinity(d) && !text.Contains("Infinity")) |
| 640 { |
| 641 throw new InvalidProtocolBufferException("Invalid numeri
c value: " + text); |
| 642 } |
| 643 return d; |
| 644 case FieldType.Float: |
| 645 float f = ParseNumericString(text, float.Parse, true); |
| 646 // float.Parse can return +/- infinity on Mono for non-infin
ite values which are out of range for float. |
| 647 if (float.IsInfinity(f) && !text.Contains("Infinity")) |
| 648 { |
| 649 throw new InvalidProtocolBufferException("Invalid numeri
c value: " + text); |
| 650 } |
| 651 return f; |
| 652 case FieldType.Enum: |
| 653 var enumValue = field.EnumType.FindValueByName(text); |
| 654 if (enumValue == null) |
| 655 { |
| 656 throw new InvalidProtocolBufferException("Invalid enum v
alue: " + text + " for enum type: " + field.EnumType.FullName); |
| 657 } |
| 658 // Just return it as an int, and let the CLR convert it. |
| 659 return enumValue.Number; |
| 660 default: |
| 661 throw new InvalidProtocolBufferException("Unsupported conver
sion from JSON string for field type " + field.FieldType); |
| 662 } |
| 663 } |
| 664 |
| 665 /// <summary> |
| 666 /// Creates a new instance of the message type for the given field. |
| 667 /// </summary> |
| 668 private static IMessage NewMessageForField(FieldDescriptor field) |
| 669 { |
| 670 return field.MessageType.Parser.CreateTemplate(); |
| 671 } |
| 672 |
| 673 private static T ParseNumericString<T>(string text, Func<string, NumberS
tyles, IFormatProvider, T> parser, bool floatingPoint) |
| 674 { |
| 675 // TODO: Prohibit leading zeroes (but allow 0!) |
| 676 // TODO: Validate handling of "Infinity" etc. (Should be case sensit
ive, no leading whitespace etc) |
| 677 // Can't prohibit this with NumberStyles. |
| 678 if (text.StartsWith("+")) |
| 679 { |
| 680 throw new InvalidProtocolBufferException("Invalid numeric value:
" + text); |
| 681 } |
| 682 if (text.StartsWith("0") && text.Length > 1) |
| 683 { |
| 684 if (text[1] >= '0' && text[1] <= '9') |
| 685 { |
| 686 throw new InvalidProtocolBufferException("Invalid numeric va
lue: " + text); |
| 687 } |
| 688 } |
| 689 else if (text.StartsWith("-0") && text.Length > 2) |
| 690 { |
| 691 if (text[2] >= '0' && text[2] <= '9') |
| 692 { |
| 693 throw new InvalidProtocolBufferException("Invalid numeric va
lue: " + text); |
| 694 } |
| 695 } |
| 696 try |
| 697 { |
| 698 var styles = floatingPoint |
| 699 ? NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalP
oint | NumberStyles.AllowExponent |
| 700 : NumberStyles.AllowLeadingSign; |
| 701 return parser(text, styles, CultureInfo.InvariantCulture); |
| 702 } |
| 703 catch (FormatException) |
| 704 { |
| 705 throw new InvalidProtocolBufferException("Invalid numeric value
for type: " + text); |
| 706 } |
| 707 catch (OverflowException) |
| 708 { |
| 709 throw new InvalidProtocolBufferException("Value out of range: "
+ text); |
| 710 } |
| 711 } |
| 712 |
| 713 private static void MergeTimestamp(IMessage message, JsonToken token) |
| 714 { |
| 715 if (token.Type != JsonToken.TokenType.StringValue) |
| 716 { |
| 717 throw new InvalidProtocolBufferException("Expected string value
for Timestamp"); |
| 718 } |
| 719 var match = TimestampRegex.Match(token.StringValue); |
| 720 if (!match.Success) |
| 721 { |
| 722 throw new InvalidProtocolBufferException("Invalid Timestamp valu
e: " + token.StringValue); |
| 723 } |
| 724 var dateTime = match.Groups["datetime"].Value; |
| 725 var subseconds = match.Groups["subseconds"].Value; |
| 726 var offset = match.Groups["offset"].Value; |
| 727 |
| 728 try |
| 729 { |
| 730 DateTime parsed = DateTime.ParseExact( |
| 731 dateTime, |
| 732 "yyyy-MM-dd'T'HH:mm:ss", |
| 733 CultureInfo.InvariantCulture, |
| 734 DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniv
ersal); |
| 735 // TODO: It would be nice not to have to create all these object
s... easy to optimize later though. |
| 736 Timestamp timestamp = Timestamp.FromDateTime(parsed); |
| 737 int nanosToAdd = 0; |
| 738 if (subseconds != "") |
| 739 { |
| 740 // This should always work, as we've got 1-9 digits. |
| 741 int parsedFraction = int.Parse(subseconds.Substring(1), Cult
ureInfo.InvariantCulture); |
| 742 nanosToAdd = parsedFraction * SubsecondScalingFactors[subsec
onds.Length]; |
| 743 } |
| 744 int secondsToAdd = 0; |
| 745 if (offset != "Z") |
| 746 { |
| 747 // This is the amount we need to *subtract* from the local t
ime to get to UTC - hence - => +1 and vice versa. |
| 748 int sign = offset[0] == '-' ? 1 : -1; |
| 749 int hours = int.Parse(offset.Substring(1, 2), CultureInfo.In
variantCulture); |
| 750 int minutes = int.Parse(offset.Substring(4, 2)); |
| 751 int totalMinutes = hours * 60 + minutes; |
| 752 if (totalMinutes > 18 * 60) |
| 753 { |
| 754 throw new InvalidProtocolBufferException("Invalid Timest
amp value: " + token.StringValue); |
| 755 } |
| 756 if (totalMinutes == 0 && sign == 1) |
| 757 { |
| 758 // This is an offset of -00:00, which means "unknown loc
al offset". It makes no sense for a timestamp. |
| 759 throw new InvalidProtocolBufferException("Invalid Timest
amp value: " + token.StringValue); |
| 760 } |
| 761 // We need to *subtract* the offset from local time to get U
TC. |
| 762 secondsToAdd = sign * totalMinutes * 60; |
| 763 } |
| 764 // Ensure we've got the right signs. Currently unnecessary, but
easy to do. |
| 765 if (secondsToAdd < 0 && nanosToAdd > 0) |
| 766 { |
| 767 secondsToAdd++; |
| 768 nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond; |
| 769 } |
| 770 if (secondsToAdd != 0 || nanosToAdd != 0) |
| 771 { |
| 772 timestamp += new Duration { Nanos = nanosToAdd, Seconds = se
condsToAdd }; |
| 773 // The resulting timestamp after offset change would be out
of our expected range. Currently the Timestamp message doesn't validate this |
| 774 // anywhere, but we shouldn't parse it. |
| 775 if (timestamp.Seconds < Timestamp.UnixSecondsAtBclMinValue |
| timestamp.Seconds > Timestamp.UnixSecondsAtBclMaxValue) |
| 776 { |
| 777 throw new InvalidProtocolBufferException("Invalid Timest
amp value: " + token.StringValue); |
| 778 } |
| 779 } |
| 780 message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor
.SetValue(message, timestamp.Seconds); |
| 781 message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.S
etValue(message, timestamp.Nanos); |
| 782 } |
| 783 catch (FormatException) |
| 784 { |
| 785 throw new InvalidProtocolBufferException("Invalid Timestamp valu
e: " + token.StringValue); |
| 786 } |
| 787 } |
| 788 |
| 789 private static void MergeDuration(IMessage message, JsonToken token) |
| 790 { |
| 791 if (token.Type != JsonToken.TokenType.StringValue) |
| 792 { |
| 793 throw new InvalidProtocolBufferException("Expected string value
for Duration"); |
| 794 } |
| 795 var match = DurationRegex.Match(token.StringValue); |
| 796 if (!match.Success) |
| 797 { |
| 798 throw new InvalidProtocolBufferException("Invalid Duration value
: " + token.StringValue); |
| 799 } |
| 800 var sign = match.Groups["sign"].Value; |
| 801 var secondsText = match.Groups["int"].Value; |
| 802 // Prohibit leading insignficant zeroes |
| 803 if (secondsText[0] == '0' && secondsText.Length > 1) |
| 804 { |
| 805 throw new InvalidProtocolBufferException("Invalid Duration value
: " + token.StringValue); |
| 806 } |
| 807 var subseconds = match.Groups["subseconds"].Value; |
| 808 var multiplier = sign == "-" ? -1 : 1; |
| 809 |
| 810 try |
| 811 { |
| 812 long seconds = long.Parse(secondsText, CultureInfo.InvariantCult
ure); |
| 813 int nanos = 0; |
| 814 if (subseconds != "") |
| 815 { |
| 816 // This should always work, as we've got 1-9 digits. |
| 817 int parsedFraction = int.Parse(subseconds.Substring(1)); |
| 818 nanos = parsedFraction * SubsecondScalingFactors[subseconds.
Length]; |
| 819 } |
| 820 if (seconds >= Duration.MaxSeconds) |
| 821 { |
| 822 // Allow precisely 315576000000 seconds, but prohibit even 1
ns more. |
| 823 if (seconds > Duration.MaxSeconds || nanos > 0) |
| 824 { |
| 825 throw new InvalidProtocolBufferException("Invalid Durati
on value: " + token.StringValue); |
| 826 } |
| 827 } |
| 828 message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.
SetValue(message, seconds * multiplier); |
| 829 message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.Se
tValue(message, nanos * multiplier); |
| 830 } |
| 831 catch (FormatException) |
| 832 { |
| 833 throw new InvalidProtocolBufferException("Invalid Duration value
: " + token.StringValue); |
| 834 } |
| 835 } |
| 836 |
| 837 private static void MergeFieldMask(IMessage message, JsonToken token) |
| 838 { |
| 839 if (token.Type != JsonToken.TokenType.StringValue) |
| 840 { |
| 841 throw new InvalidProtocolBufferException("Expected string value
for FieldMask"); |
| 842 } |
| 843 // TODO: Do we *want* to remove empty entries? Probably okay to trea
t "" as "no paths", but "foo,,bar"? |
| 844 string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators
, StringSplitOptions.RemoveEmptyEntries); |
| 845 IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.Pat
hsFieldNumber].Accessor.GetValue(message); |
| 846 foreach (var path in jsonPaths) |
| 847 { |
| 848 messagePaths.Add(ToSnakeCase(path)); |
| 849 } |
| 850 } |
| 851 |
| 852 // Ported from src/google/protobuf/util/internal/utility.cc |
| 853 private static string ToSnakeCase(string text) |
| 854 { |
| 855 var builder = new StringBuilder(text.Length * 2); |
| 856 bool wasNotUnderscore = false; // Initialize to false for case 1 (b
elow) |
| 857 bool wasNotCap = false; |
| 858 |
| 859 for (int i = 0; i < text.Length; i++) |
| 860 { |
| 861 char c = text[i]; |
| 862 if (c >= 'A' && c <= 'Z') // ascii_isupper |
| 863 { |
| 864 // Consider when the current character B is capitalized: |
| 865 // 1) At beginning of input: "B..." => "b..." |
| 866 // (e.g. "Biscuit" => "biscuit") |
| 867 // 2) Following a lowercase: "...aB..." => "...a_b..." |
| 868 // (e.g. "gBike" => "g_bike") |
| 869 // 3) At the end of input: "...AB" => "...ab" |
| 870 // (e.g. "GoogleLAB" => "google_lab") |
| 871 // 4) Followed by a lowercase: "...ABc..." => "...a_bc..." |
| 872 // (e.g. "GBike" => "g_bike") |
| 873 if (wasNotUnderscore && // case 1 o
ut |
| 874 (wasNotCap || // case 2 in, case 3 o
ut |
| 875 (i + 1 < text.Length && // case 3 ou
t |
| 876 (text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii
_islower(text[i + 1]) |
| 877 { // case 4 in |
| 878 // We add an underscore for case 2 and case 4. |
| 879 builder.Append('_'); |
| 880 } |
| 881 // ascii_tolower, but we already know that c *is* an upper c
ase ASCII character... |
| 882 builder.Append((char) (c + 'a' - 'A')); |
| 883 wasNotUnderscore = true; |
| 884 wasNotCap = false; |
| 885 } |
| 886 else |
| 887 { |
| 888 builder.Append(c); |
| 889 wasNotUnderscore = c != '_'; |
| 890 wasNotCap = true; |
| 891 } |
| 892 } |
| 893 return builder.ToString(); |
| 894 } |
| 895 #endregion |
| 896 |
| 897 /// <summary> |
| 898 /// Settings controlling JSON parsing. |
| 899 /// </summary> |
| 900 public sealed class Settings |
| 901 { |
| 902 /// <summary> |
| 903 /// Default settings, as used by <see cref="JsonParser.Default"/>. T
his has the same default |
| 904 /// recursion limit as <see cref="CodedInputStream"/>, and an empty
type registry. |
| 905 /// </summary> |
| 906 public static Settings Default { get; } |
| 907 |
| 908 // Workaround for the Mono compiler complaining about XML comments n
ot being on |
| 909 // valid language elements. |
| 910 static Settings() |
| 911 { |
| 912 Default = new Settings(CodedInputStream.DefaultRecursionLimit); |
| 913 } |
| 914 |
| 915 /// <summary> |
| 916 /// The maximum depth of messages to parse. Note that this limit onl
y applies to parsing |
| 917 /// messages, not collections - so a message within a collection wit
hin a message only counts as |
| 918 /// depth 2, not 3. |
| 919 /// </summary> |
| 920 public int RecursionLimit { get; } |
| 921 |
| 922 /// <summary> |
| 923 /// The type registry used to parse <see cref="Any"/> messages. |
| 924 /// </summary> |
| 925 public TypeRegistry TypeRegistry { get; } |
| 926 |
| 927 /// <summary> |
| 928 /// Creates a new <see cref="Settings"/> object with the specified r
ecursion limit. |
| 929 /// </summary> |
| 930 /// <param name="recursionLimit">The maximum depth of messages to pa
rse</param> |
| 931 public Settings(int recursionLimit) : this(recursionLimit, TypeRegis
try.Empty) |
| 932 { |
| 933 } |
| 934 |
| 935 /// <summary> |
| 936 /// Creates a new <see cref="Settings"/> object with the specified r
ecursion limit and type registry. |
| 937 /// </summary> |
| 938 /// <param name="recursionLimit">The maximum depth of messages to pa
rse</param> |
| 939 /// <param name="typeRegistry">The type registry used to parse <see
cref="Any"/> messages</param> |
| 940 public Settings(int recursionLimit, TypeRegistry typeRegistry) |
| 941 { |
| 942 RecursionLimit = recursionLimit; |
| 943 TypeRegistry = Preconditions.CheckNotNull(typeRegistry, nameof(t
ypeRegistry)); |
| 944 } |
| 945 } |
| 946 } |
| 947 } |
OLD | NEW |