Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(295)

Side by Side Diff: third_party/protobuf/csharp/src/Google.Protobuf/JsonParser.cs

Issue 1842653006: Update //third_party/protobuf to version 3. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: merge Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698