-Fixed reading invalid unicode surrogate pairs

This commit is contained in:
James Newton-King
2012-04-10 20:48:42 +12:00
parent 2e1211f689
commit 436c3f9a93
8 changed files with 167 additions and 33 deletions
@@ -58,11 +58,59 @@ namespace Newtonsoft.Json.Tests
Assert.AreEqual(JsonToken.String, reader.TokenType);
string s = reader.Value.ToString();
Assert.AreEqual(2, s.Length);
StringInfo stringInfo = new StringInfo(s);
Assert.AreEqual(1, stringInfo.LengthInTextElements);
}
[Test]
public void SurrogatePairReplacement()
{
// existing good surrogate pair
Assert.AreEqual("ABC \ud800\udc00 DEF", ReadString("ABC \\ud800\\udc00 DEF"));
// invalid surrogates (two high back-to-back)
Assert.AreEqual("ABC \ufffd\ufffd DEF", ReadString("ABC \\ud800\\ud800 DEF"));
// invalid surrogates (two high back-to-back)
Assert.AreEqual("ABC \ufffd\ufffd\u1234 DEF", ReadString("ABC \\ud800\\ud800\\u1234 DEF"));
// invalid surrogates (three high back-to-back)
Assert.AreEqual("ABC \ufffd\ufffd\ufffd DEF", ReadString("ABC \\ud800\\ud800\\ud800 DEF"));
// invalid surrogates (high followed by a good surrogate pair)
Assert.AreEqual("ABC \ufffd\ud800\udc00 DEF", ReadString("ABC \\ud800\\ud800\\udc00 DEF"));
// invalid high surrogate at end of string
Assert.AreEqual("ABC \ufffd", ReadString("ABC \\ud800"));
// high surrogate not followed by low surrogate
Assert.AreEqual("ABC \ufffd DEF", ReadString("ABC \\ud800 DEF"));
// low surrogate not preceded by high surrogate
Assert.AreEqual("ABC \ufffd\ufffd DEF", ReadString("ABC \\udc00\\ud800 DEF"));
// make sure unencoded invalid surrogate characters don't make it through
Assert.AreEqual("\ufffd\ufffd\ufffd", ReadString("\udc00\ud800\ud800"));
Assert.AreEqual("ABC \ufffd\b", ReadString("ABC \\ud800\\b"));
Assert.AreEqual("ABC \ufffd ", ReadString("ABC \\ud800 "));
Assert.AreEqual("ABC \b\ufffd", ReadString("ABC \\b\\ud800"));
}
private string ReadString(string input)
{
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(@"""" + input + @""""));
JsonTextReader reader = new JsonTextReader(new StreamReader(ms));
reader.Read();
string s = (string)reader.Value;
return s;
}
[Test]
public void CloseInput()
{
@@ -2227,7 +2275,7 @@ bye", reader.Value);
Assert.IsTrue(reader.Read());
ExceptionAssert.Throws<JsonReaderException>(
"This reader's MaxDepth of 1 has been exceeded.",
"The reader's MaxDepth of 1 has been exceeded. Line 1, position 2.",
() =>
{
Assert.IsTrue(reader.Read());
@@ -1580,7 +1580,7 @@ Parameter name: arrayIndex"
[Test]
public void NumberTooBigForInt64()
{
ExceptionAssert.Throws<JsonReaderException>("JSON integer 307953220000517141511 is too large or small for an Int64.",
ExceptionAssert.Throws<JsonReaderException>("JSON integer 307953220000517141511 is too large or small for an Int64. Line 1, position 30.",
() =>
{
string json = @"{""code"": 307953220000517141511}";
@@ -2872,7 +2872,7 @@ To force JSON objects to deserialize add the JsonObjectAttribute to the type. Li
{
ExceptionAssert.Throws<JsonSerializationException>(
@"Type 'Newtonsoft.Json.Tests.Serialization.JsonSerializerTest+ISerializableTestObject' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data.
To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add to JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true.",
To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add to JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true. Line 1, position 14.",
() =>
{
JsonTypeReflector.SetFullyTrusted(false);
@@ -5679,7 +5679,7 @@ To fix this error either change the environment to be fully trusted, change the
[Test]
[ExpectedException(typeof (JsonSerializationException)
#if !NETFX_CORE
, ExpectedMessage = "No JSON content found and type 'System.Double' is not nullable."
, ExpectedMessage = "No JSON content found and type 'System.Double' is not nullable. Line 0, position 0."
#endif
)]
public void DeserializeDoubleFromEmptyString()
@@ -5690,7 +5690,7 @@ To fix this error either change the environment to be fully trusted, change the
[Test]
[ExpectedException(typeof (JsonSerializationException)
#if !NETFX_CORE
, ExpectedMessage = "No JSON content found and type 'System.StringComparison' is not nullable."
, ExpectedMessage = "No JSON content found and type 'System.StringComparison' is not nullable. Line 0, position 0."
#endif
)]
public void DeserializeEnumFromEmptyString()
@@ -5701,7 +5701,7 @@ To fix this error either change the environment to be fully trusted, change the
[Test]
[ExpectedException(typeof (JsonSerializationException)
#if !NETFX_CORE
, ExpectedMessage = "No JSON content found and type 'System.Int32' is not nullable."
, ExpectedMessage = "No JSON content found and type 'System.Int32' is not nullable. Line 0, position 0."
#endif
)]
public void DeserializeInt32FromEmptyString()
+4 -4
View File
@@ -256,7 +256,7 @@ namespace Newtonsoft.Json
// this is a little hacky because Depth increases when first property/value is written but only testing here is faster/simpler
if (_maxDepth != null && Depth + 1 > _maxDepth)
throw new JsonReaderException("This reader's MaxDepth of {0} has been exceeded.".FormatWith(CultureInfo.InvariantCulture, _maxDepth));
throw CreateReaderException(this, "The reader's MaxDepth of {0} has been exceeded.".FormatWith(CultureInfo.InvariantCulture, _maxDepth));
}
}
@@ -750,7 +750,7 @@ namespace Newtonsoft.Json
JsonContainerType currentObject = Pop();
if (GetTypeForCloseToken(endToken) != currentObject)
throw new JsonReaderException("JsonToken {0} is not valid for closing JsonType {1}.".FormatWith(CultureInfo.InvariantCulture, endToken, currentObject));
throw CreateReaderException(this, "JsonToken {0} is not valid for closing JsonType {1}.".FormatWith(CultureInfo.InvariantCulture, endToken, currentObject));
_currentState = (Peek() != JsonContainerType.None) ? State.PostValue : State.Finished;
}
@@ -777,7 +777,7 @@ namespace Newtonsoft.Json
_currentState = State.Finished;
break;
default:
throw new JsonReaderException("While setting the reader state back to current object an unexpected JsonType was encountered: {0}".FormatWith(CultureInfo.InvariantCulture, currentObject));
throw CreateReaderException(this, "While setting the reader state back to current object an unexpected JsonType was encountered: {0}".FormatWith(CultureInfo.InvariantCulture, currentObject));
}
}
@@ -823,7 +823,7 @@ namespace Newtonsoft.Json
case JsonToken.EndConstructor:
return JsonContainerType.Constructor;
default:
throw new JsonReaderException("Not a valid close JsonToken: {0}".FormatWith(CultureInfo.InvariantCulture, token));
throw CreateReaderException(this, "Not a valid close JsonToken: {0}".FormatWith(CultureInfo.InvariantCulture, token));
}
}
+85 -17
View File
@@ -52,6 +52,8 @@ namespace Newtonsoft.Json
/// </summary>
public class JsonTextReader : JsonReader, IJsonLineInfo
{
private const char UnicodeReplacementChar = '\uFFFD';
private readonly TextReader _reader;
private char[] _chars;
@@ -583,19 +585,62 @@ namespace Newtonsoft.Json
case 'u':
charPos++;
_charPos = charPos;
if (EnsureChars(4, true))
writeChar = ParseUnicode();
if (StringUtils.IsLowSurrogate(writeChar))
{
string hexValues = new string(_chars, charPos, 4);
char hexChar = Convert.ToChar(int.Parse(hexValues, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo));
writeChar = hexChar;
// low surrogate with no preceding high surrogate; this char is replaced
writeChar = UnicodeReplacementChar;
}
else if (StringUtils.IsHighSurrogate(writeChar))
{
bool anotherHighSurrogate;
charPos += 4;
}
else
{
_charPos = charPos;
throw CreateReaderException(this, "Unexpected end while parsing unicode character.");
// loop for handling situations where there are multiple consecutive high surrogates
do
{
anotherHighSurrogate = false;
// potential start of a surrogate pair
if (EnsureChars(2, true) && _chars[_charPos] == '\\' && _chars[_charPos + 1] == 'u')
{
char highSurrogate = writeChar;
_charPos += 2;
writeChar = ParseUnicode();
if (StringUtils.IsLowSurrogate(writeChar))
{
// a valid surrogate pair!
}
else if (StringUtils.IsHighSurrogate(writeChar))
{
// another high surrogate; replace current and start check over
highSurrogate = UnicodeReplacementChar;
anotherHighSurrogate = true;
}
else
{
// high surrogate not followed by low surrogate; original char is replaced
highSurrogate = UnicodeReplacementChar;
}
if (buffer == null)
buffer = GetBuffer();
WriteCharToBuffer(buffer, highSurrogate, lastWritePosition, escapeStartPos);
lastWritePosition = _charPos;
}
else
{
// there are not enough remaining chars for the low surrogate or is not follow by unicode sequence
// replace high surrogate and continue on as usual
writeChar = UnicodeReplacementChar;
}
} while (anotherHighSurrogate);
}
charPos = _charPos;
break;
default:
charPos++;
@@ -606,12 +651,7 @@ namespace Newtonsoft.Json
if (buffer == null)
buffer = GetBuffer();
if (escapeStartPos > lastWritePosition)
{
buffer.Append(_chars, lastWritePosition, escapeStartPos - lastWritePosition);
}
buffer.Append(writeChar);
WriteCharToBuffer(buffer, writeChar, lastWritePosition, escapeStartPos);
lastWritePosition = charPos;
break;
@@ -655,6 +695,34 @@ namespace Newtonsoft.Json
}
}
private void WriteCharToBuffer(StringBuffer buffer, char writeChar, int lastWritePosition, int writeToPosition)
{
if (writeToPosition > lastWritePosition)
{
buffer.Append(_chars, lastWritePosition, writeToPosition - lastWritePosition);
}
buffer.Append(writeChar);
}
private char ParseUnicode()
{
char writeChar;
if (EnsureChars(4, true))
{
string hexValues = new string(_chars, _charPos, 4);
char hexChar = Convert.ToChar(int.Parse(hexValues, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo));
writeChar = hexChar;
_charPos += 4;
}
else
{
throw CreateReaderException(this, "Unexpected end while parsing unicode character.");
}
return writeChar;
}
private void ReadNumberIntoBuffer()
{
int charPos = _charPos;
@@ -1279,7 +1347,7 @@ namespace Newtonsoft.Json
}
catch (OverflowException ex)
{
throw new JsonReaderException("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex);
throw CreateReaderException((JsonReader)this, "JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex);
}
numberType = JsonToken.Integer;
+4 -4
View File
@@ -482,17 +482,17 @@ namespace Newtonsoft.Json
private void WriteConstructorDate(JsonReader reader)
{
if (!reader.Read())
throw new Exception("Unexpected end when reading date constructor.");
throw new JsonWriterException("Unexpected end when reading date constructor.");
if (reader.TokenType != JsonToken.Integer)
throw new Exception("Unexpected token when reading date constructor. Expected Integer, got " + reader.TokenType);
throw new JsonWriterException("Unexpected token when reading date constructor. Expected Integer, got " + reader.TokenType);
long ticks = (long)reader.Value;
DateTime date = JsonConvert.ConvertJavaScriptTicksToDateTime(ticks);
if (!reader.Read())
throw new Exception("Unexpected end when reading date constructor.");
throw new JsonWriterException("Unexpected end when reading date constructor.");
if (reader.TokenType != JsonToken.EndConstructor)
throw new Exception("Unexpected token when reading date constructor. Expected EndConstructor, got " + reader.TokenType);
throw new JsonWriterException("Unexpected token when reading date constructor. Expected EndConstructor, got " + reader.TokenType);
WriteValue(date);
}
@@ -118,7 +118,7 @@ namespace Newtonsoft.Json.Serialization
if (reader.TokenType == JsonToken.None && !ReadForType(reader, contract, converter != null, false))
{
if (contract != null && !contract.IsNullable)
throw new JsonSerializationException("No JSON content found and type '{0}' is not nullable.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
throw CreateSerializationException(reader, "No JSON content found and type '{0}' is not nullable.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
return null;
}
@@ -831,7 +831,7 @@ To force JSON arrays to deserialize add the JsonArrayAttribute to the type.".For
if (!JsonTypeReflector.FullyTrusted)
{
throw new JsonSerializationException(@"Type '{0}' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data.
throw CreateSerializationException(reader, @"Type '{0}' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data.
To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add to JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true.".FormatWith(CultureInfo.InvariantCulture, objectType));
}
@@ -152,5 +152,23 @@ namespace Newtonsoft.Json.Utilities
return camelCase;
}
public static bool IsHighSurrogate(char c)
{
#if !SILVERLIGHT
return char.IsHighSurrogate(c);
#else
return (c >= 55296 && c <= 56319);
#endif
}
public static bool IsLowSurrogate(char c)
{
#if !SILVERLIGHT
return char.IsLowSurrogate(c);
#else
return (c >= 56320 && c <= 57343);
#endif
}
}
}