From e3be3e43dbb94e50d6c539d27872e17727c733ea Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 18 Nov 2012 14:36:00 +1300 Subject: [PATCH] -Added StringEscapeHandling with options to escape HTML and non-ASCII characters -Added feature to deserialize ISet properties as HashSet -Fixed ReferenceLoopHandling not being used when serializing ISerializable and dynamic values --- .../JsonTextWriterTest.cs | 63 +++++++++ Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 42 ++++++ .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 62 +++++++++ .../ReferenceLoopHandlingTests.cs | 126 ++++++++++++++++++ Src/Newtonsoft.Json/JsonConvert.cs | 9 +- Src/Newtonsoft.Json/JsonTextWriter.cs | 15 ++- Src/Newtonsoft.Json/JsonWriter.cs | 12 +- .../Newtonsoft.Json.Net20.csproj | 1 + .../Newtonsoft.Json.Net35.csproj | 1 + .../Newtonsoft.Json.Portable.csproj | 1 + .../Newtonsoft.Json.Silverlight.csproj | 1 + .../Newtonsoft.Json.WindowsPhone.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/DiagnosticsTraceWriter.cs | 17 +++ .../Serialization/JsonArrayContract.cs | 18 ++- .../JsonISerializableContract.cs | 2 +- .../JsonSerializerInternalWriter.cs | 24 +++- .../Serialization/MemoryTraceWriter.cs | 35 ++++- Src/Newtonsoft.Json/StringEscapeHandling.cs | 46 +++++++ .../Utilities/JavaScriptUtils.cs | 72 +++++++--- 22 files changed, 515 insertions(+), 38 deletions(-) create mode 100644 Src/Newtonsoft.Json/StringEscapeHandling.cs diff --git a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs index ecc808a0..581d5109 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs @@ -841,6 +841,69 @@ _____'propertyName': NaN Assert.AreEqual(@"""2000-01-01T01:01:01Z""", sw.ToString()); } + [Test] + public void HtmlStringEscapeHandling() + { + StringWriter sw = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(sw) + { + StringEscapeHandling = StringEscapeHandling.EscapeHtml + }; + + string script = @""; + + writer.WriteValue(script); + + string json = sw.ToString(); + + Assert.AreEqual(@"""\u003cscript type=\u0022text/javascript\u0022\u003ealert(\u0027hi\u0027);\u003c/script\u003e""", json); + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.AreEqual(script, reader.ReadAsString()); + + //Console.WriteLine(HttpUtility.HtmlEncode(script)); + + //System.Web.Script.Serialization.JavaScriptSerializer s = new System.Web.Script.Serialization.JavaScriptSerializer(); + //Console.WriteLine(s.Serialize(new { html = script })); + } + + [Test] + public void NonAsciiStringEscapeHandling() + { + StringWriter sw = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(sw) + { + StringEscapeHandling = StringEscapeHandling.EscapeNonAscii + }; + + string unicode = "\u5f20"; + + writer.WriteValue(unicode); + + string json = sw.ToString(); + + Assert.AreEqual(8, json.Length); + Assert.AreEqual(@"""\u5f20""", json); + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.AreEqual(unicode, reader.ReadAsString()); + + sw = new StringWriter(); + writer = new JsonTextWriter(sw) + { + StringEscapeHandling = StringEscapeHandling.Default + }; + + writer.WriteValue(unicode); + + json = sw.ToString(); + + Assert.AreEqual(3, json.Length); + Assert.AreEqual("\"\u5f20\"", json); + } + [Test] public void WriteEndOnProperty() { diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 7b5c016c..7505d312 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -291,6 +291,48 @@ namespace Newtonsoft.Json.Tests Console.WriteLine("XML: " + watch.Elapsed.TotalSeconds); } + [Test] + public void SerializeString() + { + string text = @"The general form of an HTML element is therefore: content. +Some HTML elements are defined as empty elements and take the form . +Empty elements may enclose no content, for instance, the BR tag or the inline IMG tag. +The name of an HTML element is the name used in the tags. +Note that the end tag's name is preceded by a slash character, ""/"", and that in empty elements the end tag is neither required nor allowed. +If attributes are not mentioned, default values are used in each case. + +The general form of an HTML element is therefore: content. +Some HTML elements are defined as empty elements and take the form . +Empty elements may enclose no content, for instance, the BR tag or the inline IMG tag. +The name of an HTML element is the name used in the tags. +Note that the end tag's name is preceded by a slash character, ""/"", and that in empty elements the end tag is neither required nor allowed. +If attributes are not mentioned, default values are used in each case. + +The general form of an HTML element is therefore: content. +Some HTML elements are defined as empty elements and take the form . +Empty elements may enclose no content, for instance, the BR tag or the inline IMG tag. +The name of an HTML element is the name used in the tags. +Note that the end tag's name is preceded by a slash character, ""/"", and that in empty elements the end tag is neither required nor allowed. +If attributes are not mentioned, default values are used in each case. +"; + + int interations = 1000; + + TimeOperation(() => + { + string json = null; + for (int i = 0; i < interations; i++) + { + using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(text) ?? 16)) + { + JavaScriptUtils.WriteEscapedJavaScriptString(w, text, '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default); + } + } + + return ""; + }, "New"); + } + private void SerializeSize(object value) { // this is extremely slow with 5000 interations diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index de5e070e..676a0eb9 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -76,5 +76,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.5.10.15512")] +[assembly: AssemblyFileVersion("4.5.10.15518")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 8c9fab92..7333aa27 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -7154,6 +7154,68 @@ Parameter name: value", Assert.IsNull(c2.MyNullableDecimal); } #endif + +#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE) + [Test] + public void HashSetInterface() + { + ISet s1 = new HashSet(new[] {"1", "two", "III"}); + + string json = JsonConvert.SerializeObject(s1); + + ISet s2 = JsonConvert.DeserializeObject>(json); + + Assert.AreEqual(s1.Count, s2.Count); + foreach (string s in s1) + { + Assert.IsTrue(s2.Contains(s)); + } + } +#endif + + public class NewEmployee : Employee + { + public int Age { get; set; } + + public bool ShouldSerializeName() + { + return false; + } + } + + [Test] + public void ShouldSerializeInheritedClassTest() + { + NewEmployee joe = new NewEmployee(); + joe.Name = "Joe Employee"; + joe.Age = 100; + + Employee mike = new Employee(); + mike.Name = "Mike Manager"; + mike.Manager = mike; + + joe.Manager = mike; + + //StringWriter sw = new StringWriter(); + + //XmlSerializer x = new XmlSerializer(typeof(NewEmployee)); + //x.Serialize(sw, joe); + + //Console.WriteLine(sw); + + //JavaScriptSerializer s = new JavaScriptSerializer(); + //Console.WriteLine(s.Serialize(new {html = @"; & ! ^ * ( ) ! @ # $ % ^ ' "" - , . / ; : [ { } ] ; ' - _ = + ? ` ~ \ |"})); + + string json = JsonConvert.SerializeObject(joe, Formatting.Indented); + + Assert.AreEqual(@"{ + ""Age"": 100, + ""Name"": ""Joe Employee"", + ""Manager"": { + ""Name"": ""Mike Manager"" + } +}", json); + } } #if !(SILVERLIGHT || NETFX_CORE || PORTABLE) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs index 17800bbf..940abc1f 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ReferenceLoopHandlingTests.cs @@ -23,6 +23,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion +using System; using System.Collections.Generic; #if !NETFX_CORE using NUnit.Framework; @@ -31,6 +32,11 @@ using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; #endif +#if !(NET20 || NET35 || WINDOWS_PHONE) +using System.Dynamic; +#endif +using System.Runtime.Serialization; +using Newtonsoft.Json.Tests.Linq; namespace Newtonsoft.Json.Tests.Serialization { @@ -142,6 +148,126 @@ namespace Newtonsoft.Json.Tests.Serialization ] }", json); } + +#if !(PORTABLE || SILVERLIGHT) + public class MainClass : ISerializable + { + public ChildClass Child { get; set; } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Child", Child); + } + } + + public class ChildClass : ISerializable + { + public string Name { get; set; } + public MainClass Parent { get; set; } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Parent", Parent); + info.AddValue("Name", Name); + } + } + + [Test] + public void ErrorISerializableCyclicReferenceLoop() + { + var main = new MainClass(); + var child = new ChildClass(); + + child.Name = "Child1"; + child.Parent = main; // Obvious Circular Reference + + main.Child = child; + + var settings = + new JsonSerializerSettings(); + + ExceptionAssert.Throws( + "Self referencing loop detected with type 'Newtonsoft.Json.Tests.Serialization.ReferenceLoopHandlingTests+MainClass'. Path 'Child'.", + () => JsonConvert.SerializeObject(main, settings)); + } + + [Test] + public void IgnoreISerializableCyclicReferenceLoop() + { + var main = new MainClass(); + var child = new ChildClass(); + + child.Name = "Child1"; + child.Parent = main; // Obvious Circular Reference + + main.Child = child; + + var settings = + new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; + + var c = JsonConvert.SerializeObject(main, settings); + Assert.AreEqual(@"{""Child"":{""Name"":""Child1""}}", c); + } +#endif + +#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE) + public class DictionaryDynamicObject : DynamicObject + { + public IDictionary Values { get; private set; } + + public DictionaryDynamicObject() + { + Values = new Dictionary(); + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + Values[binder.Name] = value; + return true; + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + return Values.TryGetValue(binder.Name, out result); + } + + public override IEnumerable GetDynamicMemberNames() + { + return Values.Keys; + } + } + + [Test] + public void ErrorDynamicCyclicReferenceLoop() + { + dynamic parent = new DictionaryDynamicObject(); + dynamic child = new DictionaryDynamicObject(); + parent.child = child; + child.parent = parent; + + var settings = new JsonSerializerSettings(); + + ExceptionAssert.Throws( + "Self referencing loop detected with type 'Newtonsoft.Json.Tests.Serialization.ReferenceLoopHandlingTests+DictionaryDynamicObject'. Path 'child'.", + () => JsonConvert.SerializeObject(parent, settings)); + } + + [Test] + public void IgnoreDynamicCyclicReferenceLoop() + { + dynamic parent = new DictionaryDynamicObject(); + dynamic child = new DictionaryDynamicObject(); + parent.child = child; + parent.name = "parent"; + child.parent = parent; + child.name = "child"; + + var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; + + var c = JsonConvert.SerializeObject(parent, settings); + Assert.AreEqual(@"{""child"":{""name"":""child""},""name"":""parent""}", c); + } +#endif } public class PropertyItemReferenceLoopHandling diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index 2a651de8..7cf4ab85 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -556,11 +556,14 @@ namespace Newtonsoft.Json /// Converts the to its JSON string representation. /// /// The value to convert. - /// The string delimiter character. + /// The string delimiter character. /// A JSON string representation of the . - public static string ToString(string value, char delimter) + public static string ToString(string value, char delimiter) { - return JavaScriptUtils.ToEscapedJavaScriptString(value, delimter, true); + if (delimiter != '"' && delimiter != '\'') + throw new ArgumentException("Delimiter must be a single or double quote.", "delimiter"); + + return JavaScriptUtils.ToEscapedJavaScriptString(value, delimiter, true); } /// diff --git a/Src/Newtonsoft.Json/JsonTextWriter.cs b/Src/Newtonsoft.Json/JsonTextWriter.cs index 68ab5ebb..77533155 100644 --- a/Src/Newtonsoft.Json/JsonTextWriter.cs +++ b/Src/Newtonsoft.Json/JsonTextWriter.cs @@ -206,11 +206,22 @@ namespace Newtonsoft.Json { InternalWritePropertyName(name); - JavaScriptUtils.WriteEscapedJavaScriptString(_writer, name, _quoteChar, _quoteName); + JavaScriptUtils.WriteEscapedJavaScriptString(_writer, name, _quoteChar, _quoteName, GetCharEscapeFlags(), StringEscapeHandling); _writer.Write(':'); } + private bool[] GetCharEscapeFlags() + { + if (StringEscapeHandling == StringEscapeHandling.EscapeHtml) + return JavaScriptUtils.HtmlCharEscapeFlags; + + if (_quoteChar == '"') + return JavaScriptUtils.DoubleQuoteCharEscapeFlags; + + return JavaScriptUtils.SingleQuoteCharEscapeFlags; + } + /// /// Writes indent characters. /// @@ -294,7 +305,7 @@ namespace Newtonsoft.Json if (value == null) WriteValueInternal(JsonConvert.Null, JsonToken.Null); else - JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, true); + JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, true, GetCharEscapeFlags(), StringEscapeHandling); } /// diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index 0a1fa296..1c555afb 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -209,6 +209,7 @@ namespace Newtonsoft.Json private DateFormatHandling _dateFormatHandling; private DateTimeZoneHandling _dateTimeZoneHandling; + private StringEscapeHandling _stringEscapeHandling; /// /// Indicates how JSON text output is formatted. @@ -229,7 +230,7 @@ namespace Newtonsoft.Json } /// - /// Get or set how time zones are handling when writing JSON. + /// Get or set how time zones are handling when writing JSON text. /// public DateTimeZoneHandling DateTimeZoneHandling { @@ -237,6 +238,15 @@ namespace Newtonsoft.Json set { _dateTimeZoneHandling = value; } } + /// + /// Get or set how strings are escaped when writing JSON text. + /// + public StringEscapeHandling StringEscapeHandling + { + get { return _stringEscapeHandling; } + set { _stringEscapeHandling = value; } + } + /// /// Creates an instance of the JsonWriter class. /// diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index d60c32ee..ba2a7d1e 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -109,6 +109,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index 500953ae..85281869 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -121,6 +121,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index 8cccf090..fa32a3a1 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -175,6 +175,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index 483ae3fd..e74417e7 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -197,6 +197,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj index ef4f4a77..220638f0 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj @@ -170,6 +170,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index b1295ef0..756a6507 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -117,6 +117,7 @@ + diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 837296a6..b8cabcce 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -90,7 +90,7 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.5.10.15512")] +[assembly: AssemblyFileVersion("4.5.10.15518")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/DiagnosticsTraceWriter.cs b/Src/Newtonsoft.Json/Serialization/DiagnosticsTraceWriter.cs index ea6fce9f..f9555b98 100644 --- a/Src/Newtonsoft.Json/Serialization/DiagnosticsTraceWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/DiagnosticsTraceWriter.cs @@ -5,8 +5,19 @@ using DiagnosticsTrace = System.Diagnostics.Trace; namespace Newtonsoft.Json.Serialization { + /// + /// Represents a trace writer that writes to the application's instances. + /// public class DiagnosticsTraceWriter : ITraceWriter { + /// + /// Gets the that will be used to filter the trace messages passed to the writer. + /// For example a filter level of Info will exclude Verbose messages and include Info, + /// Warning and Error messages. + /// + /// + /// The that will be used to filter the trace messages passed to the writer. + /// public TraceLevel LevelFilter { get; set; } private TraceEventType GetTraceEventType(TraceLevel level) @@ -26,6 +37,12 @@ namespace Newtonsoft.Json.Serialization } } + /// + /// Writes the specified trace level, message and optional exception. + /// + /// The at which to write this trace. + /// The trace message. + /// The trace exception. This parameter is optional. public void Trace(TraceLevel level, string message, Exception ex) { if (level == TraceLevel.Off) diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index b5952e9b..14698ef8 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -85,9 +85,11 @@ namespace Newtonsoft.Json.Serialization _isCollectionItemTypeNullableType = ReflectionUtils.IsNullableType(CollectionItemType); if (IsTypeGenericCollectionInterface(UnderlyingType)) - { CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); - } +#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE) + else if (IsTypeGenericSetnterface(UnderlyingType)) + CreatedType = ReflectionUtils.MakeGenericType(typeof(HashSet<>), CollectionItemType); +#endif IsMultidimensionalArray = (UnderlyingType.IsArray && UnderlyingType.GetArrayRank() > 1); } @@ -152,5 +154,17 @@ namespace Newtonsoft.Json.Serialization || genericDefinition == typeof(ICollection<>) || genericDefinition == typeof(IEnumerable<>)); } + +#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE) + private bool IsTypeGenericSetnterface(Type type) + { + if (!type.IsGenericType()) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(ISet<>)); + } +#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs b/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs index 560ef74d..d99a5f8b 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonISerializableContract.cs @@ -31,7 +31,7 @@ namespace Newtonsoft.Json.Serialization /// /// Contract details for a used by the . /// - public class JsonISerializableContract : JsonContract + public class JsonISerializableContract : JsonContainerContract { /// /// Gets or sets the ISerializable object constructor. diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index f51d5bf0..400cd3ab 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -682,8 +682,13 @@ To fix this error either change the environment to be fully trusted, change the foreach (SerializationEntry serializationEntry in serializationInfo) { - writer.WritePropertyName(serializationEntry.Name); - SerializeValue(writer, serializationEntry.Value, GetContractSafe(serializationEntry.Value), null, null, member); + JsonContract valueContract = GetContractSafe(serializationEntry.Value); + + if (CheckForCircularReference(writer, serializationEntry.Value, null, valueContract, contract, member)) + { + writer.WritePropertyName(serializationEntry.Name); + SerializeValue(writer, serializationEntry.Value, valueContract, null, contract, member); + } } writer.WriteEndObject(); @@ -736,12 +741,17 @@ To fix this error either change the environment to be fully trusted, change the { try { - string resolvedPropertyName = (contract.PropertyNameResolver != null) - ? contract.PropertyNameResolver(memberName) - : memberName; + JsonContract valueContract = GetContractSafe(memberValue); - writer.WritePropertyName(resolvedPropertyName); - SerializeValue(writer, memberValue, GetContractSafe(memberValue), null, null, member); + if (CheckForCircularReference(writer, memberValue, null, valueContract, contract, member)) + { + string resolvedPropertyName = (contract.PropertyNameResolver != null) + ? contract.PropertyNameResolver(memberName) + : memberName; + + writer.WritePropertyName(resolvedPropertyName); + SerializeValue(writer, memberValue, valueContract, null, contract, member); + } } catch (Exception ex) { diff --git a/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs b/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs index 768684ff..7b639db5 100644 --- a/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/MemoryTraceWriter.cs @@ -5,18 +5,39 @@ using System.Text; namespace Newtonsoft.Json.Serialization { + /// + /// Represents a trace writer that writes to memory. When the trace message limit is + /// reached then old trace messages will be removed as new messages are added. + /// public class MemoryTraceWriter : ITraceWriter { private readonly Queue _traceMessages; - + + /// + /// Gets the that will be used to filter the trace messages passed to the writer. + /// For example a filter level of Info will exclude Verbose messages and include Info, + /// Warning and Error messages. + /// + /// + /// The that will be used to filter the trace messages passed to the writer. + /// public TraceLevel LevelFilter { get; set; } - + + /// + /// Initializes a new instance of the class. + /// public MemoryTraceWriter() { LevelFilter = TraceLevel.Verbose; _traceMessages = new Queue(); } + /// + /// Writes the specified trace level, message and optional exception. + /// + /// The at which to write this trace. + /// The trace message. + /// The trace exception. This parameter is optional. public void Trace(TraceLevel level, string message, Exception ex) { string traceMessage = DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff") + " " + level.ToString("g") + " " + message; @@ -27,11 +48,21 @@ namespace Newtonsoft.Json.Serialization _traceMessages.Enqueue(traceMessage); } + /// + /// Returns an enumeration of the most recent trace messages. + /// + /// An enumeration of the most recent trace messages. public IEnumerable GetTraceMessages() { return _traceMessages; } + /// + /// Returns a of the most recent trace messages. + /// + /// + /// A of the most recent trace messages. + /// public override string ToString() { StringBuilder sb = new StringBuilder(); diff --git a/Src/Newtonsoft.Json/StringEscapeHandling.cs b/Src/Newtonsoft.Json/StringEscapeHandling.cs new file mode 100644 index 00000000..cf530a17 --- /dev/null +++ b/Src/Newtonsoft.Json/StringEscapeHandling.cs @@ -0,0 +1,46 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +namespace Newtonsoft.Json +{ + /// + /// Specifies how strings are escaped when writing JSON text. + /// + public enum StringEscapeHandling + { + /// + /// Only control characters (e.g. newline) are escaped. + /// + Default, + /// + /// All non-ASCII and control characters (e.g. newline) are escaped. + /// + EscapeNonAscii, + /// + /// HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. + /// + EscapeHtml + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index e17fc259..4247882f 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -30,14 +30,48 @@ using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Collections.Generic; +#if NET20 +using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Linq; +#endif namespace Newtonsoft.Json.Utilities { internal static class JavaScriptUtils { + internal static readonly bool[] SingleQuoteCharEscapeFlags = new bool[128]; + internal static readonly bool[] DoubleQuoteCharEscapeFlags = new bool[128]; + internal static readonly bool[] HtmlCharEscapeFlags = new bool[128]; + + static JavaScriptUtils() + { + IList escapeChars = new List + { + '\n', '\r', '\t', '\\', '\f', '\b', + }; + for (int i = 0; i < ' '; i++) + { + escapeChars.Add((char)i); + } + + foreach (var escapeChar in escapeChars.Union(new[] { '\'' })) + { + SingleQuoteCharEscapeFlags[escapeChar] = true; + } + foreach (var escapeChar in escapeChars.Union(new[] { '"' })) + { + DoubleQuoteCharEscapeFlags[escapeChar] = true; + } + foreach (var escapeChar in escapeChars.Union(new[] { '"', '\'', '<', '>', '&' })) + { + HtmlCharEscapeFlags[escapeChar] = true; + } + } + private const string EscapedUnicodeText = "!"; - public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters) + public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters, bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling) { // leading delimiter if (appendDelimiters) @@ -53,8 +87,7 @@ namespace Newtonsoft.Json.Utilities { var c = s[i]; - // don't escape standard text/numbers except '\' and the text delimiter - if (c >= ' ' && c < 128 && c != '\\' && c != delimiter) + if (c < charEscapeFlags.Length && !charEscapeFlags[c]) continue; string escapedValue; @@ -88,24 +121,27 @@ namespace Newtonsoft.Json.Utilities case '\u2029': // Paragraph Separator escapedValue = @"\u2029"; break; - case '\'': - // this charater is being used as the delimiter - escapedValue = @"\'"; - break; - case '"': - // this charater is being used as the delimiter - escapedValue = "\\\""; - break; default: - if (c <= '\u001f') + if (c < charEscapeFlags.Length || stringEscapeHandling == StringEscapeHandling.EscapeNonAscii) { - if (unicodeBuffer == null) - unicodeBuffer = new char[6]; + if (c == '\'' && stringEscapeHandling != StringEscapeHandling.EscapeHtml) + { + escapedValue = @"\'"; + } + else if (c == '"' && stringEscapeHandling != StringEscapeHandling.EscapeHtml) + { + escapedValue = @"\"""; + } + else + { + if (unicodeBuffer == null) + unicodeBuffer = new char[6]; - StringUtils.ToCharAsUnicode(c, unicodeBuffer); + StringUtils.ToCharAsUnicode(c, unicodeBuffer); - // slightly hacky but it saves multiple conditions in if test - escapedValue = EscapedUnicodeText; + // slightly hacky but it saves multiple conditions in if test + escapedValue = EscapedUnicodeText; + } } else { @@ -162,7 +198,7 @@ namespace Newtonsoft.Json.Utilities { using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(value) ?? 16)) { - WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters); + WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters, (delimiter == '"') ? DoubleQuoteCharEscapeFlags : SingleQuoteCharEscapeFlags, StringEscapeHandling.Default); return w.ToString(); } }