diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 19988473..80d3e6b1 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -2481,6 +2481,76 @@ bye", reader.Value); Assert.IsTrue(reader.Read()); #endif } + + [Test] + public void ResetJsonTextReaderErrorCount() + { + ToggleReaderError toggleReaderError = new ToggleReaderError(new StringReader("{'first':1,'second':2,'third':3}")); + JsonTextReader jsonTextReader = new JsonTextReader(toggleReaderError); + + Assert.IsTrue(jsonTextReader.Read()); + + toggleReaderError.Error = true; + + ExceptionAssert.Throws( + "Read error", + () => jsonTextReader.Read()); + ExceptionAssert.Throws( + "Read error", + () => jsonTextReader.Read()); + + toggleReaderError.Error = false; + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual("first", jsonTextReader.Value); + + toggleReaderError.Error = true; + + ExceptionAssert.Throws( + "Read error", + () => jsonTextReader.Read()); + + toggleReaderError.Error = false; + + Assert.IsTrue(jsonTextReader.Read()); + Assert.AreEqual(1, jsonTextReader.Value); + + toggleReaderError.Error = true; + + ExceptionAssert.Throws( + "Read error", + () => jsonTextReader.Read()); + ExceptionAssert.Throws( + "Read error", + () => jsonTextReader.Read()); + ExceptionAssert.Throws( + "Read error", + () => jsonTextReader.Read()); + + toggleReaderError.Error = false; + + Assert.IsFalse(jsonTextReader.Read()); + } + + public class ToggleReaderError : TextReader + { + private readonly TextReader _inner; + + public bool Error { get; set; } + + public ToggleReaderError(TextReader inner) + { + _inner = inner; + } + + public override int Read(char[] buffer, int index, int count) + { + if (Error) + throw new Exception("Read error"); + + return _inner.Read(buffer, index, 1); + } + } } public class SlowStream : Stream diff --git a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs index 05b1f3be..9100e33e 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs @@ -361,6 +361,84 @@ namespace Newtonsoft.Json.Tests.Serialization { public Nest A { get; set; } } + + [Test] + public void InfiniteErrorHandlingLoopFromInputError() + { + IList errors = new List(); + + JsonSerializer serializer = new JsonSerializer(); + serializer.Error += (sender, e) => + { + errors.Add(e.ErrorContext.Error.Message); + e.ErrorContext.Handled = true; + }; + + ErrorPerson[] result = serializer.Deserialize(new JsonTextReader(new ThrowingReader())); + + Assert.IsNull(result); + Assert.AreEqual(4, errors.Count); + Assert.AreEqual("too far", errors[0]); + Assert.AreEqual("too far", errors[1]); + Assert.AreEqual("too far", errors[2]); + Assert.AreEqual("Unexpected end when deserializing array. Path '[1023]', line 1, position 65536.", errors[3]); + } + + public class ThrowingReader : TextReader + { + int _position = 0; + static string element = "{\"FirstName\":\"Din\",\"LastName\":\"Rav\",\"Item\":{\"ItemName\":\"temp\"}}"; + bool _firstRead = true; + bool _readComma = false; + + public ThrowingReader() + { + } + + public override int Read(char[] buffer, int index, int count) + { + char[] temp = new char[buffer.Length]; + int charsRead = 0; + if (_firstRead) + { + charsRead = new StringReader("[").Read(temp, index, count); + _firstRead = false; + } + else + { + if (_readComma) + { + charsRead = new StringReader(",").Read(temp, index, count); + _readComma = false; + } + else + { + charsRead = new StringReader(element).Read(temp, index, count); + _readComma = true; + } + } + + _position += charsRead; + if (_position > 65536) + { + throw new Exception("too far"); + } + Array.Copy(temp, index, buffer, index, charsRead); + return charsRead; + } + } + } + + public class ErrorPerson + { + public string FirstName { get; set; } + public string LastName { get; set; } + public ErrorItem Item { get; set; } + } + + public class ErrorItem + { + public string ItemName { get; set; } } [JsonObject] diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 49f8a2c6..7c1a8df0 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -55,6 +55,7 @@ namespace Newtonsoft.Json private const char UnicodeReplacementChar = '\uFFFD'; private readonly TextReader _reader; + private int _readerFailureCount; private char[] _chars; private int _charsUsed; @@ -75,6 +76,8 @@ namespace Newtonsoft.Json throw new ArgumentNullException("reader"); _reader = reader; + _readerFailureCount = 0; + _lineNumber = 1; _chars = new char[4097]; @@ -323,7 +326,26 @@ namespace Newtonsoft.Json int attemptCharReadCount = _chars.Length - _charsUsed - 1; - int charsRead = _reader.Read(_chars, _charsUsed, attemptCharReadCount); + int charsRead; + + try + { + charsRead = _reader.Read(_chars, _charsUsed, attemptCharReadCount); + _readerFailureCount = 0; + } + catch + { + // to prevent infinate loop when error handling is enabled + if (!_isEndOfFile) + { + _readerFailureCount++; + if (_readerFailureCount >= 3) + _isEndOfFile = true; + } + + throw; + } + _charsUsed += charsRead; if (charsRead == 0) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs index dd686d98..145ac856 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalBase.cs @@ -101,7 +101,8 @@ namespace Newtonsoft.Json.Serialization protected bool IsErrorHandled(object currentObject, JsonContract contract, object keyValue, string path, Exception ex) { ErrorContext errorContext = GetErrorContext(currentObject, keyValue, path, ex); - contract.InvokeOnError(currentObject, Serializer.Context, errorContext); + if (contract != null) + contract.InvokeOnError(currentObject, Serializer.Context, errorContext); if (!errorContext.Handled) Serializer.OnError(new ErrorEventArgs(currentObject, errorContext)); diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index cbd3be81..de65037a 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -113,17 +113,32 @@ namespace Newtonsoft.Json.Serialization JsonContract contract = GetContractSafe(objectType); - JsonConverter converter = GetConverter(contract, null, null); - - if (reader.TokenType == JsonToken.None && !ReadForType(reader, contract, converter != null, false)) + try { - if (contract != null && !contract.IsNullable) - throw JsonSerializationException.Create(reader, "No JSON content found and type '{0}' is not nullable.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); + JsonConverter converter = GetConverter(contract, null, null); - return null; + if (reader.TokenType == JsonToken.None && !ReadForType(reader, contract, converter != null, false)) + { + if (contract != null && !contract.IsNullable) + throw JsonSerializationException.Create(reader, "No JSON content found and type '{0}' is not nullable.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); + + return null; + } + + return CreateValueNonProperty(reader, objectType, contract, converter, null); + } + catch (Exception ex) + { + if (IsErrorHandled(null, contract, null, reader.Path, ex)) + { + HandleError(reader, 0); + return null; + } + else + { + throw; + } } - - return CreateValueNonProperty(reader, objectType, contract, converter, null); } private JsonSerializerProxy GetInternalSerializer()