JsonParser.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  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. using Google.Protobuf.Reflection;
  33. using Google.Protobuf.WellKnownTypes;
  34. using System;
  35. using System.Collections;
  36. using System.Collections.Generic;
  37. using System.Globalization;
  38. using System.IO;
  39. using System.Text;
  40. using System.Text.RegularExpressions;
  41. namespace Google.Protobuf
  42. {
  43. /// <summary>
  44. /// Reflection-based converter from JSON to messages.
  45. /// </summary>
  46. /// <remarks>
  47. /// <para>
  48. /// Instances of this class are thread-safe, with no mutable state.
  49. /// </para>
  50. /// <para>
  51. /// This is a simple start to get JSON parsing working. As it's reflection-based,
  52. /// it's not as quick as baking calls into generated messages - but is a simpler implementation.
  53. /// (This code is generally not heavily optimized.)
  54. /// </para>
  55. /// </remarks>
  56. public sealed class JsonParser
  57. {
  58. // Note: using 0-9 instead of \d to ensure no non-ASCII digits.
  59. // This regex isn't a complete validator, but will remove *most* invalid input. We rely on parsing to do the rest.
  60. private static readonly Regex TimestampRegex = new Regex(@"^(?<datetime>[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9])(?<subseconds>\.[0-9]{1,9})?(?<offset>(Z|[+-][0-1][0-9]:[0-5][0-9]))$", FrameworkPortability.CompiledRegexWhereAvailable);
  61. private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledRegexWhereAvailable);
  62. private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
  63. private static readonly char[] FieldMaskPathSeparators = new[] { ',' };
  64. private static readonly JsonParser defaultInstance = new JsonParser(Settings.Default);
  65. // TODO: Consider introducing a class containing parse state of the parser, tokenizer and depth. That would simplify these handlers
  66. // and the signatures of various methods.
  67. private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
  68. WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
  69. {
  70. { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => MergeTimestamp(message, tokenizer.Next()) },
  71. { Duration.Descriptor.FullName, (parser, message, tokenizer) => MergeDuration(message, tokenizer.Next()) },
  72. { Value.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStructValue(message, tokenizer) },
  73. { ListValue.Descriptor.FullName, (parser, message, tokenizer) =>
  74. parser.MergeRepeatedField(message, message.Descriptor.Fields[ListValue.ValuesFieldNumber], tokenizer) },
  75. { Struct.Descriptor.FullName, (parser, message, tokenizer) => parser.MergeStruct(message, tokenizer) },
  76. { FieldMask.Descriptor.FullName, (parser, message, tokenizer) => MergeFieldMask(message, tokenizer.Next()) },
  77. { Int32Value.Descriptor.FullName, MergeWrapperField },
  78. { Int64Value.Descriptor.FullName, MergeWrapperField },
  79. { UInt32Value.Descriptor.FullName, MergeWrapperField },
  80. { UInt64Value.Descriptor.FullName, MergeWrapperField },
  81. { FloatValue.Descriptor.FullName, MergeWrapperField },
  82. { DoubleValue.Descriptor.FullName, MergeWrapperField },
  83. { BytesValue.Descriptor.FullName, MergeWrapperField },
  84. { StringValue.Descriptor.FullName, MergeWrapperField }
  85. };
  86. // Convenience method to avoid having to repeat the same code multiple times in the above
  87. // dictionary initialization.
  88. private static void MergeWrapperField(JsonParser parser, IMessage message, JsonTokenizer tokenizer)
  89. {
  90. parser.MergeField(message, message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber], tokenizer);
  91. }
  92. /// <summary>
  93. /// Returns a formatter using the default settings.
  94. /// </summary>
  95. public static JsonParser Default { get { return defaultInstance; } }
  96. private readonly Settings settings;
  97. /// <summary>
  98. /// Creates a new formatted with the given settings.
  99. /// </summary>
  100. /// <param name="settings">The settings.</param>
  101. public JsonParser(Settings settings)
  102. {
  103. this.settings = settings;
  104. }
  105. /// <summary>
  106. /// Parses <paramref name="json"/> and merges the information into the given message.
  107. /// </summary>
  108. /// <param name="message">The message to merge the JSON information into.</param>
  109. /// <param name="json">The JSON to parse.</param>
  110. internal void Merge(IMessage message, string json)
  111. {
  112. Merge(message, new StringReader(json));
  113. }
  114. /// <summary>
  115. /// Parses JSON read from <paramref name="jsonReader"/> and merges the information into the given message.
  116. /// </summary>
  117. /// <param name="message">The message to merge the JSON information into.</param>
  118. /// <param name="jsonReader">Reader providing the JSON to parse.</param>
  119. internal void Merge(IMessage message, TextReader jsonReader)
  120. {
  121. var tokenizer = new JsonTokenizer(jsonReader);
  122. Merge(message, tokenizer);
  123. var lastToken = tokenizer.Next();
  124. if (lastToken != JsonToken.EndDocument)
  125. {
  126. throw new InvalidProtocolBufferException("Expected end of JSON after object");
  127. }
  128. }
  129. /// <summary>
  130. /// Merges the given message using data from the given tokenizer. In most cases, the next
  131. /// token should be a "start object" token, but wrapper types and nullity can invalidate
  132. /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream
  133. /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the
  134. /// tokenizer performing that validation - but not every token stream is valid "protobuf JSON".
  135. /// </summary>
  136. private void Merge(IMessage message, JsonTokenizer tokenizer)
  137. {
  138. if (tokenizer.ObjectDepth > settings.RecursionLimit)
  139. {
  140. throw InvalidProtocolBufferException.JsonRecursionLimitExceeded();
  141. }
  142. if (message.Descriptor.IsWellKnownType)
  143. {
  144. Action<JsonParser, IMessage, JsonTokenizer> handler;
  145. if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out handler))
  146. {
  147. handler(this, message, tokenizer);
  148. return;
  149. }
  150. // Well-known types with no special handling continue in the normal way.
  151. }
  152. var token = tokenizer.Next();
  153. if (token.Type != JsonToken.TokenType.StartObject)
  154. {
  155. throw new InvalidProtocolBufferException("Expected an object");
  156. }
  157. var descriptor = message.Descriptor;
  158. var jsonFieldMap = descriptor.Fields.ByJsonName();
  159. while (true)
  160. {
  161. token = tokenizer.Next();
  162. if (token.Type == JsonToken.TokenType.EndObject)
  163. {
  164. return;
  165. }
  166. if (token.Type != JsonToken.TokenType.Name)
  167. {
  168. throw new InvalidOperationException("Unexpected token type " + token.Type);
  169. }
  170. string name = token.StringValue;
  171. FieldDescriptor field;
  172. if (jsonFieldMap.TryGetValue(name, out field))
  173. {
  174. MergeField(message, field, tokenizer);
  175. }
  176. else
  177. {
  178. // TODO: Is this what we want to do? If not, we'll need to skip the value,
  179. // which may be an object or array. (We might want to put code in the tokenizer
  180. // to do that.)
  181. throw new InvalidProtocolBufferException("Unknown field: " + name);
  182. }
  183. }
  184. }
  185. private void MergeField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
  186. {
  187. var token = tokenizer.Next();
  188. if (token.Type == JsonToken.TokenType.Null)
  189. {
  190. // Note: different from Java API, which just ignores it.
  191. // TODO: Bring it more in line? Discuss...
  192. field.Accessor.Clear(message);
  193. return;
  194. }
  195. tokenizer.PushBack(token);
  196. if (field.IsMap)
  197. {
  198. MergeMapField(message, field, tokenizer);
  199. }
  200. else if (field.IsRepeated)
  201. {
  202. MergeRepeatedField(message, field, tokenizer);
  203. }
  204. else
  205. {
  206. var value = ParseSingleValue(field, tokenizer);
  207. field.Accessor.SetValue(message, value);
  208. }
  209. }
  210. private void MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
  211. {
  212. var token = tokenizer.Next();
  213. if (token.Type != JsonToken.TokenType.StartArray)
  214. {
  215. throw new InvalidProtocolBufferException("Repeated field value was not an array. Token type: " + token.Type);
  216. }
  217. IList list = (IList) field.Accessor.GetValue(message);
  218. while (true)
  219. {
  220. token = tokenizer.Next();
  221. if (token.Type == JsonToken.TokenType.EndArray)
  222. {
  223. return;
  224. }
  225. tokenizer.PushBack(token);
  226. list.Add(ParseSingleValue(field, tokenizer));
  227. }
  228. }
  229. private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
  230. {
  231. // Map fields are always objects, even if the values are well-known types: ParseSingleValue handles those.
  232. var token = tokenizer.Next();
  233. if (token.Type != JsonToken.TokenType.StartObject)
  234. {
  235. throw new InvalidProtocolBufferException("Expected an object to populate a map");
  236. }
  237. var type = field.MessageType;
  238. var keyField = type.FindFieldByNumber(1);
  239. var valueField = type.FindFieldByNumber(2);
  240. if (keyField == null || valueField == null)
  241. {
  242. throw new InvalidProtocolBufferException("Invalid map field: " + field.FullName);
  243. }
  244. IDictionary dictionary = (IDictionary) field.Accessor.GetValue(message);
  245. while (true)
  246. {
  247. token = tokenizer.Next();
  248. if (token.Type == JsonToken.TokenType.EndObject)
  249. {
  250. return;
  251. }
  252. object key = ParseMapKey(keyField, token.StringValue);
  253. object value = ParseSingleValue(valueField, tokenizer);
  254. // TODO: Null handling
  255. dictionary[key] = value;
  256. }
  257. }
  258. private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
  259. {
  260. var token = tokenizer.Next();
  261. if (token.Type == JsonToken.TokenType.Null)
  262. {
  263. if (field.FieldType == FieldType.Message && field.MessageType.FullName == Value.Descriptor.FullName)
  264. {
  265. return new Value { NullValue = NullValue.NULL_VALUE };
  266. }
  267. return null;
  268. }
  269. var fieldType = field.FieldType;
  270. if (fieldType == FieldType.Message)
  271. {
  272. // Parse wrapper types as their constituent types.
  273. // TODO: What does this mean for null?
  274. // TODO: Detect this differently when we have dynamic messages, and put it in one place...
  275. if (field.MessageType.IsWellKnownType && field.MessageType.File == Int32Value.Descriptor.File)
  276. {
  277. field = field.MessageType.Fields[WrappersReflection.WrapperValueFieldNumber];
  278. fieldType = field.FieldType;
  279. }
  280. else
  281. {
  282. // TODO: Merge the current value in message? (Public API currently doesn't make this relevant as we don't expose merging.)
  283. tokenizer.PushBack(token);
  284. IMessage subMessage = NewMessageForField(field);
  285. Merge(subMessage, tokenizer);
  286. return subMessage;
  287. }
  288. }
  289. switch (token.Type)
  290. {
  291. case JsonToken.TokenType.True:
  292. case JsonToken.TokenType.False:
  293. if (fieldType == FieldType.Bool)
  294. {
  295. return token.Type == JsonToken.TokenType.True;
  296. }
  297. // Fall through to "we don't support this type for this case"; could duplicate the behaviour of the default
  298. // case instead, but this way we'd only need to change one place.
  299. goto default;
  300. case JsonToken.TokenType.StringValue:
  301. return ParseSingleStringValue(field, token.StringValue);
  302. // Note: not passing the number value itself here, as we may end up storing the string value in the token too.
  303. case JsonToken.TokenType.Number:
  304. return ParseSingleNumberValue(field, token);
  305. case JsonToken.TokenType.Null:
  306. throw new NotImplementedException("Haven't worked out what to do for null yet");
  307. default:
  308. throw new InvalidProtocolBufferException("Unsupported JSON token type " + token.Type + " for field type " + fieldType);
  309. }
  310. }
  311. /// <summary>
  312. /// Parses <paramref name="json"/> into a new message.
  313. /// </summary>
  314. /// <typeparam name="T">The type of message to create.</typeparam>
  315. /// <param name="json">The JSON to parse.</param>
  316. /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
  317. /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
  318. public T Parse<T>(string json) where T : IMessage, new()
  319. {
  320. return Parse<T>(new StringReader(json));
  321. }
  322. /// <summary>
  323. /// Parses JSON read from <paramref name="jsonReader"/> into a new message.
  324. /// </summary>
  325. /// <typeparam name="T">The type of message to create.</typeparam>
  326. /// <param name="jsonReader">Reader providing the JSON to parse.</param>
  327. /// <exception cref="InvalidJsonException">The JSON does not comply with RFC 7159</exception>
  328. /// <exception cref="InvalidProtocolBufferException">The JSON does not represent a Protocol Buffers message correctly</exception>
  329. public T Parse<T>(TextReader jsonReader) where T : IMessage, new()
  330. {
  331. T message = new T();
  332. Merge(message, jsonReader);
  333. return message;
  334. }
  335. private void MergeStructValue(IMessage message, JsonTokenizer tokenizer)
  336. {
  337. var firstToken = tokenizer.Next();
  338. var fields = message.Descriptor.Fields;
  339. switch (firstToken.Type)
  340. {
  341. case JsonToken.TokenType.Null:
  342. fields[Value.NullValueFieldNumber].Accessor.SetValue(message, 0);
  343. return;
  344. case JsonToken.TokenType.StringValue:
  345. fields[Value.StringValueFieldNumber].Accessor.SetValue(message, firstToken.StringValue);
  346. return;
  347. case JsonToken.TokenType.Number:
  348. fields[Value.NumberValueFieldNumber].Accessor.SetValue(message, firstToken.NumberValue);
  349. return;
  350. case JsonToken.TokenType.False:
  351. case JsonToken.TokenType.True:
  352. fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, firstToken.Type == JsonToken.TokenType.True);
  353. return;
  354. case JsonToken.TokenType.StartObject:
  355. {
  356. var field = fields[Value.StructValueFieldNumber];
  357. var structMessage = NewMessageForField(field);
  358. tokenizer.PushBack(firstToken);
  359. Merge(structMessage, tokenizer);
  360. field.Accessor.SetValue(message, structMessage);
  361. return;
  362. }
  363. case JsonToken.TokenType.StartArray:
  364. {
  365. var field = fields[Value.ListValueFieldNumber];
  366. var list = NewMessageForField(field);
  367. tokenizer.PushBack(firstToken);
  368. Merge(list, tokenizer);
  369. field.Accessor.SetValue(message, list);
  370. return;
  371. }
  372. default:
  373. throw new InvalidOperationException("Unexpected token type: " + firstToken.Type);
  374. }
  375. }
  376. private void MergeStruct(IMessage message, JsonTokenizer tokenizer)
  377. {
  378. var token = tokenizer.Next();
  379. if (token.Type != JsonToken.TokenType.StartObject)
  380. {
  381. throw new InvalidProtocolBufferException("Expected object value for Struct");
  382. }
  383. tokenizer.PushBack(token);
  384. var field = message.Descriptor.Fields[Struct.FieldsFieldNumber];
  385. MergeMapField(message, field, tokenizer);
  386. }
  387. #region Utility methods which don't depend on the state (or settings) of the parser.
  388. private static object ParseMapKey(FieldDescriptor field, string keyText)
  389. {
  390. switch (field.FieldType)
  391. {
  392. case FieldType.Bool:
  393. if (keyText == "true")
  394. {
  395. return true;
  396. }
  397. if (keyText == "false")
  398. {
  399. return false;
  400. }
  401. throw new InvalidProtocolBufferException("Invalid string for bool map key: " + keyText);
  402. case FieldType.String:
  403. return keyText;
  404. case FieldType.Int32:
  405. case FieldType.SInt32:
  406. case FieldType.SFixed32:
  407. return ParseNumericString(keyText, int.Parse, false);
  408. case FieldType.UInt32:
  409. case FieldType.Fixed32:
  410. return ParseNumericString(keyText, uint.Parse, false);
  411. case FieldType.Int64:
  412. case FieldType.SInt64:
  413. case FieldType.SFixed64:
  414. return ParseNumericString(keyText, long.Parse, false);
  415. case FieldType.UInt64:
  416. case FieldType.Fixed64:
  417. return ParseNumericString(keyText, ulong.Parse, false);
  418. default:
  419. throw new InvalidProtocolBufferException("Invalid field type for map: " + field.FieldType);
  420. }
  421. }
  422. private static object ParseSingleNumberValue(FieldDescriptor field, JsonToken token)
  423. {
  424. double value = token.NumberValue;
  425. checked
  426. {
  427. // TODO: Validate that it's actually an integer, possibly in terms of the textual representation?
  428. try
  429. {
  430. switch (field.FieldType)
  431. {
  432. case FieldType.Int32:
  433. case FieldType.SInt32:
  434. case FieldType.SFixed32:
  435. return (int) value;
  436. case FieldType.UInt32:
  437. case FieldType.Fixed32:
  438. return (uint) value;
  439. case FieldType.Int64:
  440. case FieldType.SInt64:
  441. case FieldType.SFixed64:
  442. return (long) value;
  443. case FieldType.UInt64:
  444. case FieldType.Fixed64:
  445. return (ulong) value;
  446. case FieldType.Double:
  447. return value;
  448. case FieldType.Float:
  449. if (double.IsNaN(value))
  450. {
  451. return float.NaN;
  452. }
  453. if (value > float.MaxValue || value < float.MinValue)
  454. {
  455. if (double.IsPositiveInfinity(value))
  456. {
  457. return float.PositiveInfinity;
  458. }
  459. if (double.IsNegativeInfinity(value))
  460. {
  461. return float.NegativeInfinity;
  462. }
  463. throw new InvalidProtocolBufferException("Value out of range: " + value);
  464. }
  465. return (float) value;
  466. default:
  467. throw new InvalidProtocolBufferException("Unsupported conversion from JSON number for field type " + field.FieldType);
  468. }
  469. }
  470. catch (OverflowException)
  471. {
  472. throw new InvalidProtocolBufferException("Value out of range: " + value);
  473. }
  474. }
  475. }
  476. private static object ParseSingleStringValue(FieldDescriptor field, string text)
  477. {
  478. switch (field.FieldType)
  479. {
  480. case FieldType.String:
  481. return text;
  482. case FieldType.Bytes:
  483. return ByteString.FromBase64(text);
  484. case FieldType.Int32:
  485. case FieldType.SInt32:
  486. case FieldType.SFixed32:
  487. return ParseNumericString(text, int.Parse, false);
  488. case FieldType.UInt32:
  489. case FieldType.Fixed32:
  490. return ParseNumericString(text, uint.Parse, false);
  491. case FieldType.Int64:
  492. case FieldType.SInt64:
  493. case FieldType.SFixed64:
  494. return ParseNumericString(text, long.Parse, false);
  495. case FieldType.UInt64:
  496. case FieldType.Fixed64:
  497. return ParseNumericString(text, ulong.Parse, false);
  498. case FieldType.Double:
  499. double d = ParseNumericString(text, double.Parse, true);
  500. // double.Parse can return +/- infinity on Mono for non-infinite values which are out of range for double.
  501. if (double.IsInfinity(d) && !text.Contains("Infinity"))
  502. {
  503. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  504. }
  505. return d;
  506. case FieldType.Float:
  507. float f = ParseNumericString(text, float.Parse, true);
  508. // float.Parse can return +/- infinity on Mono for non-infinite values which are out of range for float.
  509. if (float.IsInfinity(f) && !text.Contains("Infinity"))
  510. {
  511. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  512. }
  513. return f;
  514. case FieldType.Enum:
  515. var enumValue = field.EnumType.FindValueByName(text);
  516. if (enumValue == null)
  517. {
  518. throw new InvalidProtocolBufferException("Invalid enum value: " + text + " for enum type: " + field.EnumType.FullName);
  519. }
  520. // Just return it as an int, and let the CLR convert it.
  521. return enumValue.Number;
  522. default:
  523. throw new InvalidProtocolBufferException("Unsupported conversion from JSON string for field type " + field.FieldType);
  524. }
  525. }
  526. /// <summary>
  527. /// Creates a new instance of the message type for the given field.
  528. /// </summary>
  529. private static IMessage NewMessageForField(FieldDescriptor field)
  530. {
  531. return field.MessageType.Parser.CreateTemplate();
  532. }
  533. private static T ParseNumericString<T>(string text, Func<string, NumberStyles, IFormatProvider, T> parser, bool floatingPoint)
  534. {
  535. // TODO: Prohibit leading zeroes (but allow 0!)
  536. // TODO: Validate handling of "Infinity" etc. (Should be case sensitive, no leading whitespace etc)
  537. // Can't prohibit this with NumberStyles.
  538. if (text.StartsWith("+"))
  539. {
  540. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  541. }
  542. if (text.StartsWith("0") && text.Length > 1)
  543. {
  544. if (text[1] >= '0' && text[1] <= '9')
  545. {
  546. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  547. }
  548. }
  549. else if (text.StartsWith("-0") && text.Length > 2)
  550. {
  551. if (text[2] >= '0' && text[2] <= '9')
  552. {
  553. throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
  554. }
  555. }
  556. try
  557. {
  558. var styles = floatingPoint
  559. ? NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent
  560. : NumberStyles.AllowLeadingSign;
  561. return parser(text, styles, CultureInfo.InvariantCulture);
  562. }
  563. catch (FormatException)
  564. {
  565. throw new InvalidProtocolBufferException("Invalid numeric value for type: " + text);
  566. }
  567. catch (OverflowException)
  568. {
  569. throw new InvalidProtocolBufferException("Value out of range: " + text);
  570. }
  571. }
  572. private static void MergeTimestamp(IMessage message, JsonToken token)
  573. {
  574. if (token.Type != JsonToken.TokenType.StringValue)
  575. {
  576. throw new InvalidProtocolBufferException("Expected string value for Timestamp");
  577. }
  578. var match = TimestampRegex.Match(token.StringValue);
  579. if (!match.Success)
  580. {
  581. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  582. }
  583. var dateTime = match.Groups["datetime"].Value;
  584. var subseconds = match.Groups["subseconds"].Value;
  585. var offset = match.Groups["offset"].Value;
  586. try
  587. {
  588. DateTime parsed = DateTime.ParseExact(
  589. dateTime,
  590. "yyyy-MM-dd'T'HH:mm:ss",
  591. CultureInfo.InvariantCulture,
  592. DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
  593. // TODO: It would be nice not to have to create all these objects... easy to optimize later though.
  594. Timestamp timestamp = Timestamp.FromDateTime(parsed);
  595. int nanosToAdd = 0;
  596. if (subseconds != "")
  597. {
  598. // This should always work, as we've got 1-9 digits.
  599. int parsedFraction = int.Parse(subseconds.Substring(1), CultureInfo.InvariantCulture);
  600. nanosToAdd = parsedFraction * SubsecondScalingFactors[subseconds.Length];
  601. }
  602. int secondsToAdd = 0;
  603. if (offset != "Z")
  604. {
  605. // This is the amount we need to *subtract* from the local time to get to UTC - hence - => +1 and vice versa.
  606. int sign = offset[0] == '-' ? 1 : -1;
  607. int hours = int.Parse(offset.Substring(1, 2), CultureInfo.InvariantCulture);
  608. int minutes = int.Parse(offset.Substring(4, 2));
  609. int totalMinutes = hours * 60 + minutes;
  610. if (totalMinutes > 18 * 60)
  611. {
  612. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  613. }
  614. if (totalMinutes == 0 && sign == 1)
  615. {
  616. // This is an offset of -00:00, which means "unknown local offset". It makes no sense for a timestamp.
  617. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  618. }
  619. // We need to *subtract* the offset from local time to get UTC.
  620. secondsToAdd = sign * totalMinutes * 60;
  621. }
  622. // Ensure we've got the right signs. Currently unnecessary, but easy to do.
  623. if (secondsToAdd < 0 && nanosToAdd > 0)
  624. {
  625. secondsToAdd++;
  626. nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond;
  627. }
  628. if (secondsToAdd != 0 || nanosToAdd != 0)
  629. {
  630. timestamp += new Duration { Nanos = nanosToAdd, Seconds = secondsToAdd };
  631. // The resulting timestamp after offset change would be out of our expected range. Currently the Timestamp message doesn't validate this
  632. // anywhere, but we shouldn't parse it.
  633. if (timestamp.Seconds < Timestamp.UnixSecondsAtBclMinValue || timestamp.Seconds > Timestamp.UnixSecondsAtBclMaxValue)
  634. {
  635. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  636. }
  637. }
  638. message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, timestamp.Seconds);
  639. message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, timestamp.Nanos);
  640. }
  641. catch (FormatException)
  642. {
  643. throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
  644. }
  645. }
  646. private static void MergeDuration(IMessage message, JsonToken token)
  647. {
  648. if (token.Type != JsonToken.TokenType.StringValue)
  649. {
  650. throw new InvalidProtocolBufferException("Expected string value for Duration");
  651. }
  652. var match = DurationRegex.Match(token.StringValue);
  653. if (!match.Success)
  654. {
  655. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  656. }
  657. var sign = match.Groups["sign"].Value;
  658. var secondsText = match.Groups["int"].Value;
  659. // Prohibit leading insignficant zeroes
  660. if (secondsText[0] == '0' && secondsText.Length > 1)
  661. {
  662. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  663. }
  664. var subseconds = match.Groups["subseconds"].Value;
  665. var multiplier = sign == "-" ? -1 : 1;
  666. try
  667. {
  668. long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture);
  669. int nanos = 0;
  670. if (subseconds != "")
  671. {
  672. // This should always work, as we've got 1-9 digits.
  673. int parsedFraction = int.Parse(subseconds.Substring(1));
  674. nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length];
  675. }
  676. if (seconds >= Duration.MaxSeconds)
  677. {
  678. // Allow precisely 315576000000 seconds, but prohibit even 1ns more.
  679. if (seconds > Duration.MaxSeconds || nanos > 0)
  680. {
  681. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  682. }
  683. }
  684. message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds * multiplier);
  685. message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos * multiplier);
  686. }
  687. catch (FormatException)
  688. {
  689. throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
  690. }
  691. }
  692. private static void MergeFieldMask(IMessage message, JsonToken token)
  693. {
  694. if (token.Type != JsonToken.TokenType.StringValue)
  695. {
  696. throw new InvalidProtocolBufferException("Expected string value for FieldMask");
  697. }
  698. // TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"?
  699. string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators, StringSplitOptions.RemoveEmptyEntries);
  700. IList messagePaths = (IList) message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message);
  701. foreach (var path in jsonPaths)
  702. {
  703. messagePaths.Add(ToSnakeCase(path));
  704. }
  705. }
  706. // Ported from src/google/protobuf/util/internal/utility.cc
  707. private static string ToSnakeCase(string text)
  708. {
  709. var builder = new StringBuilder(text.Length * 2);
  710. bool wasNotUnderscore = false; // Initialize to false for case 1 (below)
  711. bool wasNotCap = false;
  712. for (int i = 0; i < text.Length; i++)
  713. {
  714. char c = text[i];
  715. if (c >= 'A' && c <= 'Z') // ascii_isupper
  716. {
  717. // Consider when the current character B is capitalized:
  718. // 1) At beginning of input: "B..." => "b..."
  719. // (e.g. "Biscuit" => "biscuit")
  720. // 2) Following a lowercase: "...aB..." => "...a_b..."
  721. // (e.g. "gBike" => "g_bike")
  722. // 3) At the end of input: "...AB" => "...ab"
  723. // (e.g. "GoogleLAB" => "google_lab")
  724. // 4) Followed by a lowercase: "...ABc..." => "...a_bc..."
  725. // (e.g. "GBike" => "g_bike")
  726. if (wasNotUnderscore && // case 1 out
  727. (wasNotCap || // case 2 in, case 3 out
  728. (i + 1 < text.Length && // case 3 out
  729. (text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1])
  730. { // case 4 in
  731. // We add an underscore for case 2 and case 4.
  732. builder.Append('_');
  733. }
  734. // ascii_tolower, but we already know that c *is* an upper case ASCII character...
  735. builder.Append((char) (c + 'a' - 'A'));
  736. wasNotUnderscore = true;
  737. wasNotCap = false;
  738. }
  739. else
  740. {
  741. builder.Append(c);
  742. wasNotUnderscore = c != '_';
  743. wasNotCap = true;
  744. }
  745. }
  746. return builder.ToString();
  747. }
  748. #endregion
  749. /// <summary>
  750. /// Settings controlling JSON parsing.
  751. /// </summary>
  752. public sealed class Settings
  753. {
  754. private static readonly Settings defaultInstance = new Settings(CodedInputStream.DefaultRecursionLimit);
  755. private readonly int recursionLimit;
  756. /// <summary>
  757. /// Default settings, as used by <see cref="JsonParser.Default"/>
  758. /// </summary>
  759. public static Settings Default { get { return defaultInstance; } }
  760. /// <summary>
  761. /// The maximum depth of messages to parse. Note that this limit only applies to parsing
  762. /// messages, not collections - so a message within a collection within a message only counts as
  763. /// depth 2, not 3.
  764. /// </summary>
  765. public int RecursionLimit { get { return recursionLimit; } }
  766. /// <summary>
  767. /// Creates a new <see cref="Settings"/> object with the specified recursion limit.
  768. /// </summary>
  769. /// <param name="recursionLimit">The maximum depth of messages to parse</param>
  770. public Settings(int recursionLimit)
  771. {
  772. this.recursionLimit = recursionLimit;
  773. }
  774. }
  775. }
  776. }