OLD | NEW |
1 #region Copyright notice and license | 1 #region Copyright notice and license |
2 // Protocol Buffers - Google's data interchange format | 2 // Protocol Buffers - Google's data interchange format |
3 // Copyright 2015 Google Inc. All rights reserved. | 3 // Copyright 2015 Google Inc. All rights reserved. |
4 // https://developers.google.com/protocol-buffers/ | 4 // https://developers.google.com/protocol-buffers/ |
5 // | 5 // |
6 // Redistribution and use in source and binary forms, with or without | 6 // Redistribution and use in source and binary forms, with or without |
7 // modification, are permitted provided that the following conditions are | 7 // modification, are permitted provided that the following conditions are |
8 // met: | 8 // met: |
9 // | 9 // |
10 // * Redistributions of source code must retain the above copyright | 10 // * Redistributions of source code must retain the above copyright |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
161 } | 161 } |
162 // Well-known types with no special handling continue in the nor
mal way. | 162 // Well-known types with no special handling continue in the nor
mal way. |
163 } | 163 } |
164 var token = tokenizer.Next(); | 164 var token = tokenizer.Next(); |
165 if (token.Type != JsonToken.TokenType.StartObject) | 165 if (token.Type != JsonToken.TokenType.StartObject) |
166 { | 166 { |
167 throw new InvalidProtocolBufferException("Expected an object"); | 167 throw new InvalidProtocolBufferException("Expected an object"); |
168 } | 168 } |
169 var descriptor = message.Descriptor; | 169 var descriptor = message.Descriptor; |
170 var jsonFieldMap = descriptor.Fields.ByJsonName(); | 170 var jsonFieldMap = descriptor.Fields.ByJsonName(); |
| 171 // All the oneof fields we've already accounted for - we can only se
e each of them once. |
| 172 // The set is created lazily to avoid the overhead of creating a set
for every message |
| 173 // we parsed, when oneofs are relatively rare. |
| 174 HashSet<OneofDescriptor> seenOneofs = null; |
171 while (true) | 175 while (true) |
172 { | 176 { |
173 token = tokenizer.Next(); | 177 token = tokenizer.Next(); |
174 if (token.Type == JsonToken.TokenType.EndObject) | 178 if (token.Type == JsonToken.TokenType.EndObject) |
175 { | 179 { |
176 return; | 180 return; |
177 } | 181 } |
178 if (token.Type != JsonToken.TokenType.Name) | 182 if (token.Type != JsonToken.TokenType.Name) |
179 { | 183 { |
180 throw new InvalidOperationException("Unexpected token type "
+ token.Type); | 184 throw new InvalidOperationException("Unexpected token type "
+ token.Type); |
181 } | 185 } |
182 string name = token.StringValue; | 186 string name = token.StringValue; |
183 FieldDescriptor field; | 187 FieldDescriptor field; |
184 if (jsonFieldMap.TryGetValue(name, out field)) | 188 if (jsonFieldMap.TryGetValue(name, out field)) |
185 { | 189 { |
| 190 if (field.ContainingOneof != null) |
| 191 { |
| 192 if (seenOneofs == null) |
| 193 { |
| 194 seenOneofs = new HashSet<OneofDescriptor>(); |
| 195 } |
| 196 if (!seenOneofs.Add(field.ContainingOneof)) |
| 197 { |
| 198 throw new InvalidProtocolBufferException($"Multiple
values specified for oneof {field.ContainingOneof.Name}"); |
| 199 } |
| 200 } |
186 MergeField(message, field, tokenizer); | 201 MergeField(message, field, tokenizer); |
187 } | 202 } |
188 else | 203 else |
189 { | 204 { |
190 // TODO: Is this what we want to do? If not, we'll need to s
kip the value, | 205 // 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 | 206 // which may be an object or array. (We might want to put co
de in the tokenizer |
192 // to do that.) | 207 // to do that.) |
193 throw new InvalidProtocolBufferException("Unknown field: " +
name); | 208 throw new InvalidProtocolBufferException("Unknown field: " +
name); |
194 } | 209 } |
195 } | 210 } |
196 } | 211 } |
197 | 212 |
198 private void MergeField(IMessage message, FieldDescriptor field, JsonTok
enizer tokenizer) | 213 private void MergeField(IMessage message, FieldDescriptor field, JsonTok
enizer tokenizer) |
199 { | 214 { |
200 var token = tokenizer.Next(); | 215 var token = tokenizer.Next(); |
201 if (token.Type == JsonToken.TokenType.Null) | 216 if (token.Type == JsonToken.TokenType.Null) |
202 { | 217 { |
| 218 // Clear the field if we see a null token, unless it's for a sin
gular field of type |
| 219 // google.protobuf.Value. |
203 // Note: different from Java API, which just ignores it. | 220 // Note: different from Java API, which just ignores it. |
204 // TODO: Bring it more in line? Discuss... | 221 // TODO: Bring it more in line? Discuss... |
205 field.Accessor.Clear(message); | 222 if (field.IsMap || field.IsRepeated || !IsGoogleProtobufValueFie
ld(field)) |
206 return; | 223 { |
| 224 field.Accessor.Clear(message); |
| 225 return; |
| 226 } |
207 } | 227 } |
208 tokenizer.PushBack(token); | 228 tokenizer.PushBack(token); |
209 | 229 |
210 if (field.IsMap) | 230 if (field.IsMap) |
211 { | 231 { |
212 MergeMapField(message, field, tokenizer); | 232 MergeMapField(message, field, tokenizer); |
213 } | 233 } |
214 else if (field.IsRepeated) | 234 else if (field.IsRepeated) |
215 { | 235 { |
216 MergeRepeatedField(message, field, tokenizer); | 236 MergeRepeatedField(message, field, tokenizer); |
(...skipping 15 matching lines...) Expand all Loading... |
232 | 252 |
233 IList list = (IList) field.Accessor.GetValue(message); | 253 IList list = (IList) field.Accessor.GetValue(message); |
234 while (true) | 254 while (true) |
235 { | 255 { |
236 token = tokenizer.Next(); | 256 token = tokenizer.Next(); |
237 if (token.Type == JsonToken.TokenType.EndArray) | 257 if (token.Type == JsonToken.TokenType.EndArray) |
238 { | 258 { |
239 return; | 259 return; |
240 } | 260 } |
241 tokenizer.PushBack(token); | 261 tokenizer.PushBack(token); |
| 262 if (token.Type == JsonToken.TokenType.Null) |
| 263 { |
| 264 throw new InvalidProtocolBufferException("Repeated field ele
ments cannot be null"); |
| 265 } |
242 list.Add(ParseSingleValue(field, tokenizer)); | 266 list.Add(ParseSingleValue(field, tokenizer)); |
243 } | 267 } |
244 } | 268 } |
245 | 269 |
246 private void MergeMapField(IMessage message, FieldDescriptor field, Json
Tokenizer tokenizer) | 270 private void MergeMapField(IMessage message, FieldDescriptor field, Json
Tokenizer tokenizer) |
247 { | 271 { |
248 // Map fields are always objects, even if the values are well-known
types: ParseSingleValue handles those. | 272 // Map fields are always objects, even if the values are well-known
types: ParseSingleValue handles those. |
249 var token = tokenizer.Next(); | 273 var token = tokenizer.Next(); |
250 if (token.Type != JsonToken.TokenType.StartObject) | 274 if (token.Type != JsonToken.TokenType.StartObject) |
251 { | 275 { |
(...skipping 11 matching lines...) Expand all Loading... |
263 | 287 |
264 while (true) | 288 while (true) |
265 { | 289 { |
266 token = tokenizer.Next(); | 290 token = tokenizer.Next(); |
267 if (token.Type == JsonToken.TokenType.EndObject) | 291 if (token.Type == JsonToken.TokenType.EndObject) |
268 { | 292 { |
269 return; | 293 return; |
270 } | 294 } |
271 object key = ParseMapKey(keyField, token.StringValue); | 295 object key = ParseMapKey(keyField, token.StringValue); |
272 object value = ParseSingleValue(valueField, tokenizer); | 296 object value = ParseSingleValue(valueField, tokenizer); |
273 // TODO: Null handling | 297 if (value == null) |
| 298 { |
| 299 throw new InvalidProtocolBufferException("Map values must no
t be null"); |
| 300 } |
274 dictionary[key] = value; | 301 dictionary[key] = value; |
275 } | 302 } |
276 } | 303 } |
277 | 304 |
| 305 private static bool IsGoogleProtobufValueField(FieldDescriptor field) |
| 306 { |
| 307 return field.FieldType == FieldType.Message && |
| 308 field.MessageType.FullName == Value.Descriptor.FullName; |
| 309 } |
| 310 |
278 private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tok
enizer) | 311 private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tok
enizer) |
279 { | 312 { |
280 var token = tokenizer.Next(); | 313 var token = tokenizer.Next(); |
281 if (token.Type == JsonToken.TokenType.Null) | 314 if (token.Type == JsonToken.TokenType.Null) |
282 { | 315 { |
283 if (field.FieldType == FieldType.Message && field.MessageType.Fu
llName == Value.Descriptor.FullName) | 316 // TODO: In order to support dynamic messages, we should really
build this up |
| 317 // dynamically. |
| 318 if (IsGoogleProtobufValueField(field)) |
284 { | 319 { |
285 return new Value { NullValue = NullValue.NULL_VALUE }; | 320 return Value.ForNull(); |
286 } | 321 } |
287 return null; | 322 return null; |
288 } | 323 } |
289 | 324 |
290 var fieldType = field.FieldType; | 325 var fieldType = field.FieldType; |
291 if (fieldType == FieldType.Message) | 326 if (fieldType == FieldType.Message) |
292 { | 327 { |
293 // Parse wrapper types as their constituent types. | 328 // Parse wrapper types as their constituent types. |
294 // TODO: What does this mean for null? | 329 // TODO: What does this mean for null? |
295 if (field.MessageType.IsWrapperType) | 330 if (field.MessageType.IsWrapperType) |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
332 | 367 |
333 /// <summary> | 368 /// <summary> |
334 /// Parses <paramref name="json"/> into a new message. | 369 /// Parses <paramref name="json"/> into a new message. |
335 /// </summary> | 370 /// </summary> |
336 /// <typeparam name="T">The type of message to create.</typeparam> | 371 /// <typeparam name="T">The type of message to create.</typeparam> |
337 /// <param name="json">The JSON to parse.</param> | 372 /// <param name="json">The JSON to parse.</param> |
338 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> | 373 /// <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> | 374 /// <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() | 375 public T Parse<T>(string json) where T : IMessage, new() |
341 { | 376 { |
342 Preconditions.CheckNotNull(json, nameof(json)); | 377 ProtoPreconditions.CheckNotNull(json, nameof(json)); |
343 return Parse<T>(new StringReader(json)); | 378 return Parse<T>(new StringReader(json)); |
344 } | 379 } |
345 | 380 |
346 /// <summary> | 381 /// <summary> |
347 /// Parses JSON read from <paramref name="jsonReader"/> into a new messa
ge. | 382 /// Parses JSON read from <paramref name="jsonReader"/> into a new messa
ge. |
348 /// </summary> | 383 /// </summary> |
349 /// <typeparam name="T">The type of message to create.</typeparam> | 384 /// <typeparam name="T">The type of message to create.</typeparam> |
350 /// <param name="jsonReader">Reader providing the JSON to parse.</param> | 385 /// <param name="jsonReader">Reader providing the JSON to parse.</param> |
351 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> | 386 /// <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> | 387 /// <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() | 388 public T Parse<T>(TextReader jsonReader) where T : IMessage, new() |
354 { | 389 { |
355 Preconditions.CheckNotNull(jsonReader, nameof(jsonReader)); | 390 ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader)); |
356 T message = new T(); | 391 T message = new T(); |
357 Merge(message, jsonReader); | 392 Merge(message, jsonReader); |
358 return message; | 393 return message; |
359 } | 394 } |
360 | 395 |
361 /// <summary> | 396 /// <summary> |
362 /// Parses <paramref name="json"/> into a new message. | 397 /// Parses <paramref name="json"/> into a new message. |
363 /// </summary> | 398 /// </summary> |
364 /// <param name="json">The JSON to parse.</param> | 399 /// <param name="json">The JSON to parse.</param> |
365 /// <param name="descriptor">Descriptor of message type to parse.</param
> | 400 /// <param name="descriptor">Descriptor of message type to parse.</param
> |
366 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> | 401 /// <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> | 402 /// <exception cref="InvalidProtocolBufferException">The JSON does not r
epresent a Protocol Buffers message correctly</exception> |
368 public IMessage Parse(string json, MessageDescriptor descriptor) | 403 public IMessage Parse(string json, MessageDescriptor descriptor) |
369 { | 404 { |
370 Preconditions.CheckNotNull(json, nameof(json)); | 405 ProtoPreconditions.CheckNotNull(json, nameof(json)); |
371 Preconditions.CheckNotNull(descriptor, nameof(descriptor)); | 406 ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor)); |
372 return Parse(new StringReader(json), descriptor); | 407 return Parse(new StringReader(json), descriptor); |
373 } | 408 } |
374 | 409 |
375 /// <summary> | 410 /// <summary> |
376 /// Parses JSON read from <paramref name="jsonReader"/> into a new messa
ge. | 411 /// Parses JSON read from <paramref name="jsonReader"/> into a new messa
ge. |
377 /// </summary> | 412 /// </summary> |
378 /// <param name="jsonReader">Reader providing the JSON to parse.</param> | 413 /// <param name="jsonReader">Reader providing the JSON to parse.</param> |
379 /// <param name="descriptor">Descriptor of message type to parse.</param
> | 414 /// <param name="descriptor">Descriptor of message type to parse.</param
> |
380 /// <exception cref="InvalidJsonException">The JSON does not comply with
RFC 7159</exception> | 415 /// <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> | 416 /// <exception cref="InvalidProtocolBufferException">The JSON does not r
epresent a Protocol Buffers message correctly</exception> |
382 public IMessage Parse(TextReader jsonReader, MessageDescriptor descripto
r) | 417 public IMessage Parse(TextReader jsonReader, MessageDescriptor descripto
r) |
383 { | 418 { |
384 Preconditions.CheckNotNull(jsonReader, nameof(jsonReader)); | 419 ProtoPreconditions.CheckNotNull(jsonReader, nameof(jsonReader)); |
385 Preconditions.CheckNotNull(descriptor, nameof(descriptor)); | 420 ProtoPreconditions.CheckNotNull(descriptor, nameof(descriptor)); |
386 IMessage message = descriptor.Parser.CreateTemplate(); | 421 IMessage message = descriptor.Parser.CreateTemplate(); |
387 Merge(message, jsonReader); | 422 Merge(message, jsonReader); |
388 return message; | 423 return message; |
389 } | 424 } |
390 | 425 |
391 private void MergeStructValue(IMessage message, JsonTokenizer tokenizer) | 426 private void MergeStructValue(IMessage message, JsonTokenizer tokenizer) |
392 { | 427 { |
393 var firstToken = tokenizer.Next(); | 428 var firstToken = tokenizer.Next(); |
394 var fields = message.Descriptor.Fields; | 429 var fields = message.Descriptor.Fields; |
395 switch (firstToken.Type) | 430 switch (firstToken.Type) |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
457 int typeUrlObjectDepth = tokenizer.ObjectDepth; | 492 int typeUrlObjectDepth = tokenizer.ObjectDepth; |
458 | 493 |
459 // The check for the property depth protects us from nested Any valu
es which occur before the type URL | 494 // The check for the property depth protects us from nested Any valu
es which occur before the type URL |
460 // for *this* Any. | 495 // for *this* Any. |
461 while (token.Type != JsonToken.TokenType.Name || | 496 while (token.Type != JsonToken.TokenType.Name || |
462 token.StringValue != JsonFormatter.AnyTypeUrlField || | 497 token.StringValue != JsonFormatter.AnyTypeUrlField || |
463 tokenizer.ObjectDepth != typeUrlObjectDepth) | 498 tokenizer.ObjectDepth != typeUrlObjectDepth) |
464 { | 499 { |
465 tokens.Add(token); | 500 tokens.Add(token); |
466 token = tokenizer.Next(); | 501 token = tokenizer.Next(); |
| 502 |
| 503 if (tokenizer.ObjectDepth < typeUrlObjectDepth) |
| 504 { |
| 505 throw new InvalidProtocolBufferException("Any message with n
o @type"); |
| 506 } |
467 } | 507 } |
468 | 508 |
469 // Don't add the @type property or its value to the recorded token l
ist | 509 // Don't add the @type property or its value to the recorded token l
ist |
470 token = tokenizer.Next(); | 510 token = tokenizer.Next(); |
471 if (token.Type != JsonToken.TokenType.StringValue) | 511 if (token.Type != JsonToken.TokenType.StringValue) |
472 { | 512 { |
473 throw new InvalidProtocolBufferException("Expected string value
for Any.@type"); | 513 throw new InvalidProtocolBufferException("Expected string value
for Any.@type"); |
474 } | 514 } |
475 string typeUrl = token.StringValue; | 515 string typeUrl = token.StringValue; |
476 string typeName = JsonFormatter.GetTypeName(typeUrl); | 516 string typeName = Any.GetTypeName(typeUrl); |
477 | 517 |
478 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName); | 518 MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName); |
479 if (descriptor == null) | 519 if (descriptor == null) |
480 { | 520 { |
481 throw new InvalidOperationException($"Type registry has no descr
iptor for type name '{typeName}'"); | 521 throw new InvalidOperationException($"Type registry has no descr
iptor for type name '{typeName}'"); |
482 } | 522 } |
483 | 523 |
484 // Now replay the token stream we've already read and anything that
remains of the object, just parsing it | 524 // 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. | 525 // as normal. Our original tokenizer should end up at the end of the
object. |
486 var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer); | 526 var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer); |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
533 if (keyText == "false") | 573 if (keyText == "false") |
534 { | 574 { |
535 return false; | 575 return false; |
536 } | 576 } |
537 throw new InvalidProtocolBufferException("Invalid string for
bool map key: " + keyText); | 577 throw new InvalidProtocolBufferException("Invalid string for
bool map key: " + keyText); |
538 case FieldType.String: | 578 case FieldType.String: |
539 return keyText; | 579 return keyText; |
540 case FieldType.Int32: | 580 case FieldType.Int32: |
541 case FieldType.SInt32: | 581 case FieldType.SInt32: |
542 case FieldType.SFixed32: | 582 case FieldType.SFixed32: |
543 return ParseNumericString(keyText, int.Parse, false); | 583 return ParseNumericString(keyText, int.Parse); |
544 case FieldType.UInt32: | 584 case FieldType.UInt32: |
545 case FieldType.Fixed32: | 585 case FieldType.Fixed32: |
546 return ParseNumericString(keyText, uint.Parse, false); | 586 return ParseNumericString(keyText, uint.Parse); |
547 case FieldType.Int64: | 587 case FieldType.Int64: |
548 case FieldType.SInt64: | 588 case FieldType.SInt64: |
549 case FieldType.SFixed64: | 589 case FieldType.SFixed64: |
550 return ParseNumericString(keyText, long.Parse, false); | 590 return ParseNumericString(keyText, long.Parse); |
551 case FieldType.UInt64: | 591 case FieldType.UInt64: |
552 case FieldType.Fixed64: | 592 case FieldType.Fixed64: |
553 return ParseNumericString(keyText, ulong.Parse, false); | 593 return ParseNumericString(keyText, ulong.Parse); |
554 default: | 594 default: |
555 throw new InvalidProtocolBufferException("Invalid field type
for map: " + field.FieldType); | 595 throw new InvalidProtocolBufferException("Invalid field type
for map: " + field.FieldType); |
556 } | 596 } |
557 } | 597 } |
558 | 598 |
559 private static object ParseSingleNumberValue(FieldDescriptor field, Json
Token token) | 599 private static object ParseSingleNumberValue(FieldDescriptor field, Json
Token token) |
560 { | 600 { |
561 double value = token.NumberValue; | 601 double value = token.NumberValue; |
562 checked | 602 checked |
563 { | 603 { |
564 // TODO: Validate that it's actually an integer, possibly in ter
ms of the textual representation? | |
565 try | 604 try |
566 { | 605 { |
567 switch (field.FieldType) | 606 switch (field.FieldType) |
568 { | 607 { |
569 case FieldType.Int32: | 608 case FieldType.Int32: |
570 case FieldType.SInt32: | 609 case FieldType.SInt32: |
571 case FieldType.SFixed32: | 610 case FieldType.SFixed32: |
| 611 CheckInteger(value); |
572 return (int) value; | 612 return (int) value; |
573 case FieldType.UInt32: | 613 case FieldType.UInt32: |
574 case FieldType.Fixed32: | 614 case FieldType.Fixed32: |
| 615 CheckInteger(value); |
575 return (uint) value; | 616 return (uint) value; |
576 case FieldType.Int64: | 617 case FieldType.Int64: |
577 case FieldType.SInt64: | 618 case FieldType.SInt64: |
578 case FieldType.SFixed64: | 619 case FieldType.SFixed64: |
| 620 CheckInteger(value); |
579 return (long) value; | 621 return (long) value; |
580 case FieldType.UInt64: | 622 case FieldType.UInt64: |
581 case FieldType.Fixed64: | 623 case FieldType.Fixed64: |
| 624 CheckInteger(value); |
582 return (ulong) value; | 625 return (ulong) value; |
583 case FieldType.Double: | 626 case FieldType.Double: |
584 return value; | 627 return value; |
585 case FieldType.Float: | 628 case FieldType.Float: |
586 if (double.IsNaN(value)) | 629 if (double.IsNaN(value)) |
587 { | 630 { |
588 return float.NaN; | 631 return float.NaN; |
589 } | 632 } |
590 if (value > float.MaxValue || value < float.MinValue
) | 633 if (value > float.MaxValue || value < float.MinValue
) |
591 { | 634 { |
592 if (double.IsPositiveInfinity(value)) | 635 if (double.IsPositiveInfinity(value)) |
593 { | 636 { |
594 return float.PositiveInfinity; | 637 return float.PositiveInfinity; |
595 } | 638 } |
596 if (double.IsNegativeInfinity(value)) | 639 if (double.IsNegativeInfinity(value)) |
597 { | 640 { |
598 return float.NegativeInfinity; | 641 return float.NegativeInfinity; |
599 } | 642 } |
600 throw new InvalidProtocolBufferException("Value
out of range: " + value); | 643 throw new InvalidProtocolBufferException($"Value
out of range: {value}"); |
601 } | 644 } |
602 return (float) value; | 645 return (float) value; |
| 646 case FieldType.Enum: |
| 647 CheckInteger(value); |
| 648 // Just return it as an int, and let the CLR convert
it. |
| 649 // Note that we deliberately don't check that it's a
known value. |
| 650 return (int) value; |
603 default: | 651 default: |
604 throw new InvalidProtocolBufferException("Unsupporte
d conversion from JSON number for field type " + field.FieldType); | 652 throw new InvalidProtocolBufferException($"Unsupport
ed conversion from JSON number for field type {field.FieldType}"); |
605 } | 653 } |
606 } | 654 } |
607 catch (OverflowException) | 655 catch (OverflowException) |
608 { | 656 { |
609 throw new InvalidProtocolBufferException("Value out of range
: " + value); | 657 throw new InvalidProtocolBufferException($"Value out of rang
e: {value}"); |
610 } | 658 } |
611 } | 659 } |
612 } | 660 } |
613 | 661 |
| 662 private static void CheckInteger(double value) |
| 663 { |
| 664 if (double.IsInfinity(value) || double.IsNaN(value)) |
| 665 { |
| 666 throw new InvalidProtocolBufferException($"Value not an integer:
{value}"); |
| 667 } |
| 668 if (value != Math.Floor(value)) |
| 669 { |
| 670 throw new InvalidProtocolBufferException($"Value not an integer:
{value}"); |
| 671 } |
| 672 } |
| 673 |
614 private static object ParseSingleStringValue(FieldDescriptor field, stri
ng text) | 674 private static object ParseSingleStringValue(FieldDescriptor field, stri
ng text) |
615 { | 675 { |
616 switch (field.FieldType) | 676 switch (field.FieldType) |
617 { | 677 { |
618 case FieldType.String: | 678 case FieldType.String: |
619 return text; | 679 return text; |
620 case FieldType.Bytes: | 680 case FieldType.Bytes: |
621 return ByteString.FromBase64(text); | 681 try |
| 682 { |
| 683 return ByteString.FromBase64(text); |
| 684 } |
| 685 catch (FormatException e) |
| 686 { |
| 687 throw InvalidProtocolBufferException.InvalidBase64(e); |
| 688 } |
622 case FieldType.Int32: | 689 case FieldType.Int32: |
623 case FieldType.SInt32: | 690 case FieldType.SInt32: |
624 case FieldType.SFixed32: | 691 case FieldType.SFixed32: |
625 return ParseNumericString(text, int.Parse, false); | 692 return ParseNumericString(text, int.Parse); |
626 case FieldType.UInt32: | 693 case FieldType.UInt32: |
627 case FieldType.Fixed32: | 694 case FieldType.Fixed32: |
628 return ParseNumericString(text, uint.Parse, false); | 695 return ParseNumericString(text, uint.Parse); |
629 case FieldType.Int64: | 696 case FieldType.Int64: |
630 case FieldType.SInt64: | 697 case FieldType.SInt64: |
631 case FieldType.SFixed64: | 698 case FieldType.SFixed64: |
632 return ParseNumericString(text, long.Parse, false); | 699 return ParseNumericString(text, long.Parse); |
633 case FieldType.UInt64: | 700 case FieldType.UInt64: |
634 case FieldType.Fixed64: | 701 case FieldType.Fixed64: |
635 return ParseNumericString(text, ulong.Parse, false); | 702 return ParseNumericString(text, ulong.Parse); |
636 case FieldType.Double: | 703 case FieldType.Double: |
637 double d = ParseNumericString(text, double.Parse, true); | 704 double d = ParseNumericString(text, double.Parse); |
638 // double.Parse can return +/- infinity on Mono for non-infi
nite values which are out of range for double. | 705 ValidateInfinityAndNan(text, double.IsPositiveInfinity(d), d
ouble.IsNegativeInfinity(d), double.IsNaN(d)); |
639 if (double.IsInfinity(d) && !text.Contains("Infinity")) | |
640 { | |
641 throw new InvalidProtocolBufferException("Invalid numeri
c value: " + text); | |
642 } | |
643 return d; | 706 return d; |
644 case FieldType.Float: | 707 case FieldType.Float: |
645 float f = ParseNumericString(text, float.Parse, true); | 708 float f = ParseNumericString(text, float.Parse); |
646 // float.Parse can return +/- infinity on Mono for non-infin
ite values which are out of range for float. | 709 ValidateInfinityAndNan(text, float.IsPositiveInfinity(f), fl
oat.IsNegativeInfinity(f), float.IsNaN(f)); |
647 if (float.IsInfinity(f) && !text.Contains("Infinity")) | |
648 { | |
649 throw new InvalidProtocolBufferException("Invalid numeri
c value: " + text); | |
650 } | |
651 return f; | 710 return f; |
652 case FieldType.Enum: | 711 case FieldType.Enum: |
653 var enumValue = field.EnumType.FindValueByName(text); | 712 var enumValue = field.EnumType.FindValueByName(text); |
654 if (enumValue == null) | 713 if (enumValue == null) |
655 { | 714 { |
656 throw new InvalidProtocolBufferException("Invalid enum v
alue: " + text + " for enum type: " + field.EnumType.FullName); | 715 throw new InvalidProtocolBufferException($"Invalid enum
value: {text} for enum type: {field.EnumType.FullName}"); |
657 } | 716 } |
658 // Just return it as an int, and let the CLR convert it. | 717 // Just return it as an int, and let the CLR convert it. |
659 return enumValue.Number; | 718 return enumValue.Number; |
660 default: | 719 default: |
661 throw new InvalidProtocolBufferException("Unsupported conver
sion from JSON string for field type " + field.FieldType); | 720 throw new InvalidProtocolBufferException($"Unsupported conve
rsion from JSON string for field type {field.FieldType}"); |
662 } | 721 } |
663 } | 722 } |
664 | 723 |
665 /// <summary> | 724 /// <summary> |
666 /// Creates a new instance of the message type for the given field. | 725 /// Creates a new instance of the message type for the given field. |
667 /// </summary> | 726 /// </summary> |
668 private static IMessage NewMessageForField(FieldDescriptor field) | 727 private static IMessage NewMessageForField(FieldDescriptor field) |
669 { | 728 { |
670 return field.MessageType.Parser.CreateTemplate(); | 729 return field.MessageType.Parser.CreateTemplate(); |
671 } | 730 } |
672 | 731 |
673 private static T ParseNumericString<T>(string text, Func<string, NumberS
tyles, IFormatProvider, T> parser, bool floatingPoint) | 732 private static T ParseNumericString<T>(string text, Func<string, NumberS
tyles, IFormatProvider, T> parser) |
674 { | 733 { |
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. | 734 // Can't prohibit this with NumberStyles. |
678 if (text.StartsWith("+")) | 735 if (text.StartsWith("+")) |
679 { | 736 { |
680 throw new InvalidProtocolBufferException("Invalid numeric value:
" + text); | 737 throw new InvalidProtocolBufferException($"Invalid numeric value
: {text}"); |
681 } | 738 } |
682 if (text.StartsWith("0") && text.Length > 1) | 739 if (text.StartsWith("0") && text.Length > 1) |
683 { | 740 { |
684 if (text[1] >= '0' && text[1] <= '9') | 741 if (text[1] >= '0' && text[1] <= '9') |
685 { | 742 { |
686 throw new InvalidProtocolBufferException("Invalid numeric va
lue: " + text); | 743 throw new InvalidProtocolBufferException($"Invalid numeric v
alue: {text}"); |
687 } | 744 } |
688 } | 745 } |
689 else if (text.StartsWith("-0") && text.Length > 2) | 746 else if (text.StartsWith("-0") && text.Length > 2) |
690 { | 747 { |
691 if (text[2] >= '0' && text[2] <= '9') | 748 if (text[2] >= '0' && text[2] <= '9') |
692 { | 749 { |
693 throw new InvalidProtocolBufferException("Invalid numeric va
lue: " + text); | 750 throw new InvalidProtocolBufferException($"Invalid numeric v
alue: {text}"); |
694 } | 751 } |
695 } | 752 } |
696 try | 753 try |
697 { | 754 { |
698 var styles = floatingPoint | 755 return parser(text, NumberStyles.AllowLeadingSign | NumberStyles
.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); |
699 ? NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalP
oint | NumberStyles.AllowExponent | |
700 : NumberStyles.AllowLeadingSign; | |
701 return parser(text, styles, CultureInfo.InvariantCulture); | |
702 } | 756 } |
703 catch (FormatException) | 757 catch (FormatException) |
704 { | 758 { |
705 throw new InvalidProtocolBufferException("Invalid numeric value
for type: " + text); | 759 throw new InvalidProtocolBufferException($"Invalid numeric value
for type: {text}"); |
706 } | 760 } |
707 catch (OverflowException) | 761 catch (OverflowException) |
708 { | 762 { |
709 throw new InvalidProtocolBufferException("Value out of range: "
+ text); | 763 throw new InvalidProtocolBufferException($"Value out of range: {
text}"); |
710 } | 764 } |
711 } | 765 } |
712 | 766 |
| 767 /// <summary> |
| 768 /// Checks that any infinite/NaN values originated from the correct text
. |
| 769 /// This corrects the lenient whitespace handling of double.Parse/float.
Parse, as well as the |
| 770 /// way that Mono parses out-of-range values as infinity. |
| 771 /// </summary> |
| 772 private static void ValidateInfinityAndNan(string text, bool isPositiveI
nfinity, bool isNegativeInfinity, bool isNaN) |
| 773 { |
| 774 if ((isPositiveInfinity && text != "Infinity") || |
| 775 (isNegativeInfinity && text != "-Infinity") || |
| 776 (isNaN && text != "NaN")) |
| 777 { |
| 778 throw new InvalidProtocolBufferException($"Invalid numeric value
: {text}"); |
| 779 } |
| 780 } |
| 781 |
713 private static void MergeTimestamp(IMessage message, JsonToken token) | 782 private static void MergeTimestamp(IMessage message, JsonToken token) |
714 { | 783 { |
715 if (token.Type != JsonToken.TokenType.StringValue) | 784 if (token.Type != JsonToken.TokenType.StringValue) |
716 { | 785 { |
717 throw new InvalidProtocolBufferException("Expected string value
for Timestamp"); | 786 throw new InvalidProtocolBufferException("Expected string value
for Timestamp"); |
718 } | 787 } |
719 var match = TimestampRegex.Match(token.StringValue); | 788 var match = TimestampRegex.Match(token.StringValue); |
720 if (!match.Success) | 789 if (!match.Success) |
721 { | 790 { |
722 throw new InvalidProtocolBufferException("Invalid Timestamp valu
e: " + token.StringValue); | 791 throw new InvalidProtocolBufferException($"Invalid Timestamp val
ue: {token.StringValue}"); |
723 } | 792 } |
724 var dateTime = match.Groups["datetime"].Value; | 793 var dateTime = match.Groups["datetime"].Value; |
725 var subseconds = match.Groups["subseconds"].Value; | 794 var subseconds = match.Groups["subseconds"].Value; |
726 var offset = match.Groups["offset"].Value; | 795 var offset = match.Groups["offset"].Value; |
727 | 796 |
728 try | 797 try |
729 { | 798 { |
730 DateTime parsed = DateTime.ParseExact( | 799 DateTime parsed = DateTime.ParseExact( |
731 dateTime, | 800 dateTime, |
732 "yyyy-MM-dd'T'HH:mm:ss", | 801 "yyyy-MM-dd'T'HH:mm:ss", |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
802 // Prohibit leading insignficant zeroes | 871 // Prohibit leading insignficant zeroes |
803 if (secondsText[0] == '0' && secondsText.Length > 1) | 872 if (secondsText[0] == '0' && secondsText.Length > 1) |
804 { | 873 { |
805 throw new InvalidProtocolBufferException("Invalid Duration value
: " + token.StringValue); | 874 throw new InvalidProtocolBufferException("Invalid Duration value
: " + token.StringValue); |
806 } | 875 } |
807 var subseconds = match.Groups["subseconds"].Value; | 876 var subseconds = match.Groups["subseconds"].Value; |
808 var multiplier = sign == "-" ? -1 : 1; | 877 var multiplier = sign == "-" ? -1 : 1; |
809 | 878 |
810 try | 879 try |
811 { | 880 { |
812 long seconds = long.Parse(secondsText, CultureInfo.InvariantCult
ure); | 881 long seconds = long.Parse(secondsText, CultureInfo.InvariantCult
ure) * multiplier; |
813 int nanos = 0; | 882 int nanos = 0; |
814 if (subseconds != "") | 883 if (subseconds != "") |
815 { | 884 { |
816 // This should always work, as we've got 1-9 digits. | 885 // This should always work, as we've got 1-9 digits. |
817 int parsedFraction = int.Parse(subseconds.Substring(1)); | 886 int parsedFraction = int.Parse(subseconds.Substring(1)); |
818 nanos = parsedFraction * SubsecondScalingFactors[subseconds.
Length]; | 887 nanos = parsedFraction * SubsecondScalingFactors[subseconds.
Length] * multiplier; |
819 } | 888 } |
820 if (seconds >= Duration.MaxSeconds) | 889 if (!Duration.IsNormalized(seconds, nanos)) |
821 { | 890 { |
822 // Allow precisely 315576000000 seconds, but prohibit even 1
ns more. | 891 throw new InvalidProtocolBufferException($"Invalid Duration
value: {token.StringValue}"); |
823 if (seconds > Duration.MaxSeconds || nanos > 0) | |
824 { | |
825 throw new InvalidProtocolBufferException("Invalid Durati
on value: " + token.StringValue); | |
826 } | |
827 } | 892 } |
828 message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.
SetValue(message, seconds * multiplier); | 893 message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.
SetValue(message, seconds); |
829 message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.Se
tValue(message, nanos * multiplier); | 894 message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.Se
tValue(message, nanos); |
830 } | 895 } |
831 catch (FormatException) | 896 catch (FormatException) |
832 { | 897 { |
833 throw new InvalidProtocolBufferException("Invalid Duration value
: " + token.StringValue); | 898 throw new InvalidProtocolBufferException($"Invalid Duration valu
e: {token.StringValue}"); |
834 } | 899 } |
835 } | 900 } |
836 | 901 |
837 private static void MergeFieldMask(IMessage message, JsonToken token) | 902 private static void MergeFieldMask(IMessage message, JsonToken token) |
838 { | 903 { |
839 if (token.Type != JsonToken.TokenType.StringValue) | 904 if (token.Type != JsonToken.TokenType.StringValue) |
840 { | 905 { |
841 throw new InvalidProtocolBufferException("Expected string value
for FieldMask"); | 906 throw new InvalidProtocolBufferException("Expected string value
for FieldMask"); |
842 } | 907 } |
843 // TODO: Do we *want* to remove empty entries? Probably okay to trea
t "" as "no paths", but "foo,,bar"? | 908 // 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); | 909 string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators
, StringSplitOptions.RemoveEmptyEntries); |
845 IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.Pat
hsFieldNumber].Accessor.GetValue(message); | 910 IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.Pat
hsFieldNumber].Accessor.GetValue(message); |
846 foreach (var path in jsonPaths) | 911 foreach (var path in jsonPaths) |
847 { | 912 { |
848 messagePaths.Add(ToSnakeCase(path)); | 913 messagePaths.Add(ToSnakeCase(path)); |
849 } | 914 } |
850 } | 915 } |
851 | 916 |
852 // Ported from src/google/protobuf/util/internal/utility.cc | 917 // Ported from src/google/protobuf/util/internal/utility.cc |
853 private static string ToSnakeCase(string text) | 918 private static string ToSnakeCase(string text) |
854 { | 919 { |
855 var builder = new StringBuilder(text.Length * 2); | 920 var builder = new StringBuilder(text.Length * 2); |
| 921 // Note: this is probably unnecessary now, but currently retained to
be as close as possible to the |
| 922 // C++, whilst still throwing an exception on underscores. |
856 bool wasNotUnderscore = false; // Initialize to false for case 1 (b
elow) | 923 bool wasNotUnderscore = false; // Initialize to false for case 1 (b
elow) |
857 bool wasNotCap = false; | 924 bool wasNotCap = false; |
858 | 925 |
859 for (int i = 0; i < text.Length; i++) | 926 for (int i = 0; i < text.Length; i++) |
860 { | 927 { |
861 char c = text[i]; | 928 char c = text[i]; |
862 if (c >= 'A' && c <= 'Z') // ascii_isupper | 929 if (c >= 'A' && c <= 'Z') // ascii_isupper |
863 { | 930 { |
864 // Consider when the current character B is capitalized: | 931 // Consider when the current character B is capitalized: |
865 // 1) At beginning of input: "B..." => "b..." | 932 // 1) At beginning of input: "B..." => "b..." |
(...skipping 13 matching lines...) Expand all Loading... |
879 builder.Append('_'); | 946 builder.Append('_'); |
880 } | 947 } |
881 // ascii_tolower, but we already know that c *is* an upper c
ase ASCII character... | 948 // ascii_tolower, but we already know that c *is* an upper c
ase ASCII character... |
882 builder.Append((char) (c + 'a' - 'A')); | 949 builder.Append((char) (c + 'a' - 'A')); |
883 wasNotUnderscore = true; | 950 wasNotUnderscore = true; |
884 wasNotCap = false; | 951 wasNotCap = false; |
885 } | 952 } |
886 else | 953 else |
887 { | 954 { |
888 builder.Append(c); | 955 builder.Append(c); |
889 wasNotUnderscore = c != '_'; | 956 if (c == '_') |
| 957 { |
| 958 throw new InvalidProtocolBufferException($"Invalid field
mask: {text}"); |
| 959 } |
| 960 wasNotUnderscore = true; |
890 wasNotCap = true; | 961 wasNotCap = true; |
891 } | 962 } |
892 } | 963 } |
893 return builder.ToString(); | 964 return builder.ToString(); |
894 } | 965 } |
895 #endregion | 966 #endregion |
896 | 967 |
897 /// <summary> | 968 /// <summary> |
898 /// Settings controlling JSON parsing. | 969 /// Settings controlling JSON parsing. |
899 /// </summary> | 970 /// </summary> |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
933 } | 1004 } |
934 | 1005 |
935 /// <summary> | 1006 /// <summary> |
936 /// Creates a new <see cref="Settings"/> object with the specified r
ecursion limit and type registry. | 1007 /// Creates a new <see cref="Settings"/> object with the specified r
ecursion limit and type registry. |
937 /// </summary> | 1008 /// </summary> |
938 /// <param name="recursionLimit">The maximum depth of messages to pa
rse</param> | 1009 /// <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> | 1010 /// <param name="typeRegistry">The type registry used to parse <see
cref="Any"/> messages</param> |
940 public Settings(int recursionLimit, TypeRegistry typeRegistry) | 1011 public Settings(int recursionLimit, TypeRegistry typeRegistry) |
941 { | 1012 { |
942 RecursionLimit = recursionLimit; | 1013 RecursionLimit = recursionLimit; |
943 TypeRegistry = Preconditions.CheckNotNull(typeRegistry, nameof(t
ypeRegistry)); | 1014 TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nam
eof(typeRegistry)); |
944 } | 1015 } |
945 } | 1016 } |
946 } | 1017 } |
947 } | 1018 } |
OLD | NEW |