1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 
5 namespace System.Runtime.Serialization.Json
6 {
7     using System.Diagnostics.CodeAnalysis;
8     using System.Globalization;
9     using System.IO;
10     using System.Runtime;
11     using System.Runtime.Serialization;
12     using System.Security;
13 #if !MONO
14     using System.ServiceModel;
15 #endif
16     using System.Text;
17     using System.Xml;
18 
19     class XmlJsonWriter : XmlDictionaryWriter, IXmlJsonWriterInitializer
20     {
21         const char BACK_SLASH = '\\';
22         const char FORWARD_SLASH = '/';
23 
24         const char HIGH_SURROGATE_START = (char)0xd800;
25         const char LOW_SURROGATE_END = (char)0xdfff;
26         const char MAX_CHAR = (char)0xfffe;
27         const char WHITESPACE = ' ';
28         const char CARRIAGE_RETURN = '\r';
29         const char NEWLINE = '\n';
30         const char BACKSPACE = '\b';
31         const char FORM_FEED = '\f';
32         const char HORIZONTAL_TABULATION = '\t';
33         const string xmlNamespace = "http://www.w3.org/XML/1998/namespace";
34         const string xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
35 
36         [Fx.Tag.SecurityNote(Critical = "Static fields are marked SecurityCritical or readonly to prevent"
37             + " data from being modified or leaked to other components in appdomain.")]
38         [SecurityCritical]
39         static BinHexEncoding binHexEncoding;
40 
41         // This array was part of a perf improvement for escaping characters < WHITESPACE.
42         static char[] CharacterAbbrevs;
43 
44         string attributeText;
45         JsonDataType dataType;
46         int depth;
47         bool endElementBuffer;
48         bool isWritingDataTypeAttribute;
49         bool isWritingServerTypeAttribute;
50         bool isWritingXmlnsAttribute;
51         bool isWritingXmlnsAttributeDefaultNs;
52         NameState nameState;
53         JsonNodeType nodeType;
54         JsonNodeWriter nodeWriter;
55         JsonNodeType[] scopes;
56         string serverTypeValue;
57         // Do not use this field's value anywhere other than the WriteState property.
58         // It's OK to set this field's value anywhere and then change the WriteState property appropriately.
59         // If it's necessary to check the WriteState outside WriteState, use the WriteState property.
60         WriteState writeState;
61         bool wroteServerTypeAttribute;
62         bool indent;
63         string indentChars;
64         int indentLevel;
65 
XmlJsonWriter()66         public XmlJsonWriter() : this(false, null) { }
67 
XmlJsonWriter(bool indent, string indentChars)68         public XmlJsonWriter(bool indent, string indentChars)
69         {
70             this.indent = indent;
71             if (indent)
72             {
73                 if (indentChars == null)
74                 {
75                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("indentChars");
76                 }
77                 this.indentChars = indentChars;
78             }
79             InitializeWriter();
80 
81             if (CharacterAbbrevs == null)
82             {
83                 CharacterAbbrevs = GetCharacterAbbrevs();
84             }
85         }
86 
GetCharacterAbbrevs()87         private static char[] GetCharacterAbbrevs()
88         {
89             var abbrevs = new char[WHITESPACE];
90             for(int i = 0; i < WHITESPACE; i++)
91             {
92                 char abbrev;
93                 if (!LocalAppContextSwitches.DoNotUseEcmaScriptV6EscapeControlCharacter && TryEscapeControlCharacter((char)i, out abbrev))
94                 {
95                     abbrevs[i] = abbrev;
96                 }
97                 else
98                 {
99                     abbrevs[i] = (char) 0;
100                 }
101             }
102 
103             return abbrevs;
104         }
105 
TryEscapeControlCharacter(char ch, out char abbrev)106         private static bool TryEscapeControlCharacter(char ch, out char abbrev)
107         {
108             switch (ch)
109             {
110                 case BACKSPACE:
111                     abbrev = 'b';
112                     break;
113                 case HORIZONTAL_TABULATION:
114                     abbrev = 't';
115                     break;
116                 case NEWLINE:
117                     abbrev = 'n';
118                     break;
119                 case FORM_FEED:
120                     abbrev = 'f';
121                     break;
122                 case CARRIAGE_RETURN:
123                     abbrev = 'r';
124                     break;
125                 default:
126                     abbrev = ' ';
127                     return false;
128             }
129 
130             return true;
131         }
132 
133         enum JsonDataType
134         {
135             None,
136             Null,
137             Boolean,
138             Number,
139             String,
140             Object,
141             Array
142         };
143 
144         [Flags]
145         enum NameState
146         {
147             None = 0,
148             IsWritingNameWithMapping = 1,
149             IsWritingNameAttribute = 2,
150             WrittenNameWithMapping = 4,
151         }
152 
153         public override XmlWriterSettings Settings
154         {
155             // The XmlWriterSettings object used to create this writer instance.
156             // If this writer was not created using the Create method, this property
157             // returns a null reference.
158             get { return null; }
159         }
160 
161         public override WriteState WriteState
162         {
163             get
164             {
165                 if (writeState == WriteState.Closed)
166                 {
167                     return WriteState.Closed;
168                 }
169                 if (HasOpenAttribute)
170                 {
171                     return WriteState.Attribute;
172                 }
173                 switch (nodeType)
174                 {
175                     case JsonNodeType.None:
176                         return WriteState.Start;
177                     case JsonNodeType.Element:
178                         return WriteState.Element;
179                     case JsonNodeType.QuotedText:
180                     case JsonNodeType.StandaloneText:
181                     case JsonNodeType.EndElement:
182                         return WriteState.Content;
183                     default:
184                         return WriteState.Error;
185                 }
186             }
187         }
188 
189         public override string XmlLang
190         {
191             get { return null; }
192         }
193 
194         public override XmlSpace XmlSpace
195         {
196             get { return XmlSpace.None; }
197         }
198 
199         static BinHexEncoding BinHexEncoding
200         {
201             [Fx.Tag.SecurityNote(Critical = "Fetches the critical binHexEncoding field.",
202                 Safe = "Get-only properties only need to be protected for write; initialized in getter if null.")]
203             [SecuritySafeCritical]
204             get
205             {
206                 if (binHexEncoding == null)
207                 {
208                     binHexEncoding = new BinHexEncoding();
209                 }
210                 return binHexEncoding;
211             }
212         }
213 
214         bool HasOpenAttribute
215         {
216             get
217             {
218                 return (isWritingDataTypeAttribute || isWritingServerTypeAttribute || IsWritingNameAttribute || isWritingXmlnsAttribute);
219             }
220         }
221 
222         bool IsClosed
223         {
224             get { return (WriteState == WriteState.Closed); }
225         }
226 
227         bool IsWritingCollection
228         {
229             get { return (depth > 0) && (scopes[depth] == JsonNodeType.Collection); }
230         }
231 
232         bool IsWritingNameAttribute
233         {
234             get { return (nameState & NameState.IsWritingNameAttribute) == NameState.IsWritingNameAttribute; }
235         }
236 
237         bool IsWritingNameWithMapping
238         {
239             get { return (nameState & NameState.IsWritingNameWithMapping) == NameState.IsWritingNameWithMapping; }
240         }
241 
242         bool WrittenNameWithMapping
243         {
244             get { return (nameState & NameState.WrittenNameWithMapping) == NameState.WrittenNameWithMapping; }
245         }
246 
Close()247         public override void Close()
248         {
249             if (!IsClosed)
250             {
251                 try
252                 {
253                     WriteEndDocument();
254                 }
255                 finally
256                 {
257                     try
258                     {
259                         nodeWriter.Flush();
260                         nodeWriter.Close();
261                     }
262                     finally
263                     {
264                         writeState = WriteState.Closed;
265                         if (depth != 0)
266                         {
267                             depth = 0;
268                         }
269                     }
270                 }
271             }
272         }
273 
Flush()274         public override void Flush()
275         {
276             if (IsClosed)
277             {
278                 ThrowClosed();
279             }
280             nodeWriter.Flush();
281         }
282 
LookupPrefix(string ns)283         public override string LookupPrefix(string ns)
284         {
285             if (ns == null)
286             {
287                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("ns");
288             }
289             if (ns == Globals.XmlnsNamespace)
290             {
291                 return Globals.XmlnsPrefix;
292             }
293             if (ns == xmlNamespace)
294             {
295                 return JsonGlobals.xmlPrefix;
296             }
297             if (ns == string.Empty)
298             {
299                 return string.Empty;
300             }
301             return null;
302         }
303 
SetOutput(Stream stream, Encoding encoding, bool ownsStream)304         public void SetOutput(Stream stream, Encoding encoding, bool ownsStream)
305         {
306             if (stream == null)
307             {
308                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
309             }
310             if (encoding == null)
311             {
312                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encoding");
313             }
314             if (encoding.WebName != Encoding.UTF8.WebName)
315             {
316                 stream = new JsonEncodingStreamWrapper(stream, encoding, false);
317             }
318             else
319             {
320                 encoding = null;
321             }
322             if (nodeWriter == null)
323             {
324                 nodeWriter = new JsonNodeWriter();
325             }
326 
327             nodeWriter.SetOutput(stream, ownsStream, encoding);
328             InitializeWriter();
329         }
330 
WriteArray(string prefix, string localName, string namespaceUri, bool[] array, int offset, int count)331         public override void WriteArray(string prefix, string localName, string namespaceUri, bool[] array, int offset, int count)
332         {
333             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
334         }
335 
WriteArray(string prefix, string localName, string namespaceUri, Int16[] array, int offset, int count)336         public override void WriteArray(string prefix, string localName, string namespaceUri, Int16[] array, int offset, int count)
337         {
338             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
339         }
340 
WriteArray(string prefix, string localName, string namespaceUri, Int32[] array, int offset, int count)341         public override void WriteArray(string prefix, string localName, string namespaceUri, Int32[] array, int offset, int count)
342         {
343             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
344         }
345 
WriteArray(string prefix, string localName, string namespaceUri, Int64[] array, int offset, int count)346         public override void WriteArray(string prefix, string localName, string namespaceUri, Int64[] array, int offset, int count)
347         {
348             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
349         }
350 
WriteArray(string prefix, string localName, string namespaceUri, float[] array, int offset, int count)351         public override void WriteArray(string prefix, string localName, string namespaceUri, float[] array, int offset, int count)
352         {
353             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
354         }
355 
WriteArray(string prefix, string localName, string namespaceUri, double[] array, int offset, int count)356         public override void WriteArray(string prefix, string localName, string namespaceUri, double[] array, int offset, int count)
357         {
358             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
359         }
360 
WriteArray(string prefix, string localName, string namespaceUri, decimal[] array, int offset, int count)361         public override void WriteArray(string prefix, string localName, string namespaceUri, decimal[] array, int offset, int count)
362         {
363             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
364         }
365 
WriteArray(string prefix, string localName, string namespaceUri, DateTime[] array, int offset, int count)366         public override void WriteArray(string prefix, string localName, string namespaceUri, DateTime[] array, int offset, int count)
367         {
368             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
369         }
370 
WriteArray(string prefix, string localName, string namespaceUri, Guid[] array, int offset, int count)371         public override void WriteArray(string prefix, string localName, string namespaceUri, Guid[] array, int offset, int count)
372         {
373             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
374         }
375 
WriteArray(string prefix, string localName, string namespaceUri, TimeSpan[] array, int offset, int count)376         public override void WriteArray(string prefix, string localName, string namespaceUri, TimeSpan[] array, int offset, int count)
377         {
378             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
379         }
380 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, bool[] array, int offset, int count)381         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, bool[] array, int offset, int count)
382         {
383             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
384         }
385 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, decimal[] array, int offset, int count)386         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, decimal[] array, int offset, int count)
387         {
388             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
389         }
390 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, double[] array, int offset, int count)391         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, double[] array, int offset, int count)
392         {
393             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
394         }
395 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, float[] array, int offset, int count)396         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, float[] array, int offset, int count)
397         {
398             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
399         }
400 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, int[] array, int offset, int count)401         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, int[] array, int offset, int count)
402         {
403             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
404         }
405 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, long[] array, int offset, int count)406         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, long[] array, int offset, int count)
407         {
408             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
409         }
410 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, short[] array, int offset, int count)411         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, short[] array, int offset, int count)
412         {
413             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
414         }
415 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, DateTime[] array, int offset, int count)416         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, DateTime[] array, int offset, int count)
417         {
418             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
419         }
420 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, Guid[] array, int offset, int count)421         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, Guid[] array, int offset, int count)
422         {
423             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
424         }
425 
WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, TimeSpan[] array, int offset, int count)426         public override void WriteArray(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, TimeSpan[] array, int offset, int count)
427         {
428             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonWriteArrayNotSupported)));
429         }
430 
WriteBase64(byte[] buffer, int index, int count)431         public override void WriteBase64(byte[] buffer, int index, int count)
432         {
433             if (buffer == null)
434             {
435                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
436             }
437 
438             // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
439             if (index < 0)
440             {
441                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
442                     new ArgumentOutOfRangeException("index", SR.GetString(SR.ValueMustBeNonNegative)));
443             }
444 
445             if (count < 0)
446             {
447                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
448                     new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
449             }
450             if (count > buffer.Length - index)
451             {
452                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
453                     new ArgumentOutOfRangeException("count",
454                     SR.GetString(SR.JsonSizeExceedsRemainingBufferSpace,
455                     buffer.Length - index)));
456             }
457 
458             StartText();
459             nodeWriter.WriteBase64Text(buffer, 0, buffer, index, count);
460         }
461 
WriteBinHex(byte[] buffer, int index, int count)462         public override void WriteBinHex(byte[] buffer, int index, int count)
463         {
464             if (buffer == null)
465             {
466                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
467             }
468 
469             // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
470             if (index < 0)
471             {
472                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
473                     new ArgumentOutOfRangeException("index", SR.GetString(SR.ValueMustBeNonNegative)));
474             }
475 
476             if (count < 0)
477             {
478                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
479                     new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
480             }
481             if (count > buffer.Length - index)
482             {
483                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
484                     new ArgumentOutOfRangeException("count",
485                     SR.GetString(SR.JsonSizeExceedsRemainingBufferSpace,
486                     buffer.Length - index)));
487             }
488 
489             StartText();
490             WriteEscapedJsonString(BinHexEncoding.GetString(buffer, index, count));
491         }
492 
WriteCData(string text)493         public override void WriteCData(string text)
494         {
495             WriteString(text);
496         }
497 
WriteCharEntity(char ch)498         public override void WriteCharEntity(char ch)
499         {
500             WriteString(ch.ToString());
501         }
502 
WriteChars(char[] buffer, int index, int count)503         public override void WriteChars(char[] buffer, int index, int count)
504         {
505             if (buffer == null)
506             {
507                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
508             }
509 
510             // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
511             if (index < 0)
512             {
513                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
514                     new ArgumentOutOfRangeException("index", SR.GetString(SR.ValueMustBeNonNegative)));
515             }
516 
517             if (count < 0)
518             {
519                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
520                     new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
521             }
522             if (count > buffer.Length - index)
523             {
524                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
525                     new ArgumentOutOfRangeException("count",
526                     SR.GetString(SR.JsonSizeExceedsRemainingBufferSpace,
527                     buffer.Length - index)));
528             }
529 
530             WriteString(new string(buffer, index, count));
531         }
532 
WriteComment(string text)533         public override void WriteComment(string text)
534         {
535             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteComment")));
536         }
537 
538         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "2#sysid", Justification = "This method is derived from the base")]
539         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "1#pubid", Justification = "This method is derived from the base")]
WriteDocType(string name, string pubid, string sysid, string subset)540         public override void WriteDocType(string name, string pubid, string sysid, string subset)
541         {
542             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteDocType")));
543         }
544 
WriteEndAttribute()545         public override void WriteEndAttribute()
546         {
547             if (IsClosed)
548             {
549                 ThrowClosed();
550             }
551             if (!HasOpenAttribute)
552             {
553                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
554                     new XmlException(SR.GetString(SR.JsonNoMatchingStartAttribute)));
555             }
556 
557             Fx.Assert(!(isWritingDataTypeAttribute && isWritingServerTypeAttribute),
558                 "Can not write type attribute and __type attribute at the same time.");
559 
560             if (isWritingDataTypeAttribute)
561             {
562                 switch (attributeText)
563                 {
564                     case JsonGlobals.numberString:
565                         {
566                             ThrowIfServerTypeWritten(JsonGlobals.numberString);
567                             dataType = JsonDataType.Number;
568                             break;
569                         }
570                     case JsonGlobals.stringString:
571                         {
572                             ThrowIfServerTypeWritten(JsonGlobals.stringString);
573                             dataType = JsonDataType.String;
574                             break;
575                         }
576                     case JsonGlobals.arrayString:
577                         {
578                             ThrowIfServerTypeWritten(JsonGlobals.arrayString);
579                             dataType = JsonDataType.Array;
580                             break;
581                         }
582                     case JsonGlobals.objectString:
583                         {
584                             dataType = JsonDataType.Object;
585                             break;
586                         }
587                     case JsonGlobals.nullString:
588                         {
589                             ThrowIfServerTypeWritten(JsonGlobals.nullString);
590                             dataType = JsonDataType.Null;
591                             break;
592                         }
593                     case JsonGlobals.booleanString:
594                         {
595                             ThrowIfServerTypeWritten(JsonGlobals.booleanString);
596                             dataType = JsonDataType.Boolean;
597                             break;
598                         }
599                     default:
600                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
601                             new XmlException(SR.GetString(SR.JsonUnexpectedAttributeValue, attributeText)));
602                 }
603 
604                 attributeText = null;
605                 isWritingDataTypeAttribute = false;
606 
607                 if (!IsWritingNameWithMapping || WrittenNameWithMapping)
608                 {
609                     WriteDataTypeServerType();
610                 }
611             }
612             else if (isWritingServerTypeAttribute)
613             {
614                 serverTypeValue = attributeText;
615                 attributeText = null;
616                 isWritingServerTypeAttribute = false;
617 
618                 // we are writing __type after type="object" (enforced by WSE)
619                 if ((!IsWritingNameWithMapping || WrittenNameWithMapping) && dataType == JsonDataType.Object)
620                 {
621                     WriteServerTypeAttribute();
622                 }
623             }
624             else if (IsWritingNameAttribute)
625             {
626                 WriteJsonElementName(attributeText);
627                 attributeText = null;
628                 nameState = NameState.IsWritingNameWithMapping | NameState.WrittenNameWithMapping;
629                 WriteDataTypeServerType();
630             }
631             else if (isWritingXmlnsAttribute)
632             {
633                 if (!string.IsNullOrEmpty(attributeText) && isWritingXmlnsAttributeDefaultNs)
634                 {
635                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("ns", SR.GetString(SR.JsonNamespaceMustBeEmpty, attributeText));
636                 }
637 
638                 attributeText = null;
639                 isWritingXmlnsAttribute = false;
640                 isWritingXmlnsAttributeDefaultNs = false;
641             }
642         }
643 
WriteEndDocument()644         public override void WriteEndDocument()
645         {
646             if (IsClosed)
647             {
648                 ThrowClosed();
649             }
650             if (nodeType != JsonNodeType.None)
651             {
652                 while (depth > 0)
653                 {
654                     WriteEndElement();
655                 }
656             }
657         }
658 
WriteEndElement()659         public override void WriteEndElement()
660         {
661             if (IsClosed)
662             {
663                 ThrowClosed();
664             }
665 
666             if (depth == 0)
667             {
668                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
669                     new XmlException(SR.GetString(SR.JsonEndElementNoOpenNodes)));
670             }
671             if (HasOpenAttribute)
672             {
673                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
674                     new XmlException(SR.GetString(SR.JsonOpenAttributeMustBeClosedFirst, "WriteEndElement")));
675             }
676 
677             endElementBuffer = false;
678 
679             JsonNodeType token = ExitScope();
680             if (token == JsonNodeType.Collection)
681             {
682                 indentLevel--;
683                 if (indent)
684                 {
685                     if (nodeType == JsonNodeType.Element)
686                     {
687                         nodeWriter.WriteText(WHITESPACE);
688                     }
689                     else
690                     {
691                         WriteNewLine();
692                         WriteIndent();
693                     }
694                 }
695                 nodeWriter.WriteText(JsonGlobals.EndCollectionChar);
696                 token = ExitScope();
697             }
698             else if (nodeType == JsonNodeType.QuotedText)
699             {
700                 // For writing "
701                 WriteJsonQuote();
702             }
703             else if (nodeType == JsonNodeType.Element)
704             {
705                 if ((dataType == JsonDataType.None) && (serverTypeValue != null))
706                 {
707                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
708                         new XmlException(SR.GetString(SR.JsonMustSpecifyDataType,
709                         JsonGlobals.typeString, JsonGlobals.objectString, JsonGlobals.serverTypeString)));
710                 }
711 
712                 if (IsWritingNameWithMapping && !WrittenNameWithMapping)
713                 {
714                     // Ending </item> without writing item attribute
715                     // Not providing a better error message because localization deadline has passed.
716                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
717                         new XmlException(SR.GetString(SR.JsonMustSpecifyDataType,
718                         JsonGlobals.itemString, string.Empty, JsonGlobals.itemString)));
719                 }
720 
721                 // the element is empty, it does not have any content,
722                 if ((dataType == JsonDataType.None) ||
723                     (dataType == JsonDataType.String))
724                 {
725                     nodeWriter.WriteText(JsonGlobals.QuoteChar);
726                     nodeWriter.WriteText(JsonGlobals.QuoteChar);
727                 }
728             }
729             else
730             {
731                 // Assert on only StandaloneText and EndElement because preceding if
732                 //    conditions take care of checking for QuotedText and Element.
733                 Fx.Assert((nodeType == JsonNodeType.StandaloneText) || (nodeType == JsonNodeType.EndElement),
734                     "nodeType has invalid value " + nodeType + ". Expected it to be QuotedText, Element, StandaloneText, or EndElement.");
735             }
736             if (depth != 0)
737             {
738                 if (token == JsonNodeType.Element)
739                 {
740                     endElementBuffer = true;
741                 }
742                 else if (token == JsonNodeType.Object)
743                 {
744                     indentLevel--;
745                     if (indent)
746                     {
747                         if (nodeType == JsonNodeType.Element)
748                         {
749                             nodeWriter.WriteText(WHITESPACE);
750                         }
751                         else
752                         {
753                             WriteNewLine();
754                             WriteIndent();
755                         }
756                     }
757                     nodeWriter.WriteText(JsonGlobals.EndObjectChar);
758                     if ((depth > 0) && scopes[depth] == JsonNodeType.Element)
759                     {
760                         ExitScope();
761                         endElementBuffer = true;
762                     }
763                 }
764             }
765 
766             dataType = JsonDataType.None;
767             nodeType = JsonNodeType.EndElement;
768             nameState = NameState.None;
769             wroteServerTypeAttribute = false;
770         }
771 
WriteEntityRef(string name)772         public override void WriteEntityRef(string name)
773         {
774             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteEntityRef")));
775         }
776 
WriteFullEndElement()777         public override void WriteFullEndElement()
778         {
779             WriteEndElement();
780         }
781 
WriteProcessingInstruction(string name, string text)782         public override void WriteProcessingInstruction(string name, string text)
783         {
784             if (IsClosed)
785             {
786                 ThrowClosed();
787             }
788 
789             if (!name.Equals("xml", StringComparison.OrdinalIgnoreCase))
790             {
791                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.JsonXmlProcessingInstructionNotSupported), "name"));
792             }
793 
794             if (WriteState != WriteState.Start)
795             {
796                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.JsonXmlInvalidDeclaration)));
797             }
798         }
799 
WriteQualifiedName(string localName, string ns)800         public override void WriteQualifiedName(string localName, string ns)
801         {
802             if (localName == null)
803             {
804                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localName");
805             }
806             if (localName.Length == 0)
807             {
808                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("localName",
809                     SR.GetString(SR.JsonInvalidLocalNameEmpty));
810             }
811             if (ns == null)
812             {
813                 ns = string.Empty;
814             }
815 
816             base.WriteQualifiedName(localName, ns);
817         }
818 
WriteRaw(string data)819         public override void WriteRaw(string data)
820         {
821             WriteString(data);
822         }
823 
WriteRaw(char[] buffer, int index, int count)824         public override void WriteRaw(char[] buffer, int index, int count)
825         {
826             if (buffer == null)
827             {
828                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
829             }
830 
831             // Not checking upper bound because it will be caught by "count".  This is what XmlTextWriter does.
832             if (index < 0)
833             {
834                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
835                     new ArgumentOutOfRangeException("index", SR.GetString(SR.ValueMustBeNonNegative)));
836             }
837 
838             if (count < 0)
839             {
840                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
841                     new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
842             }
843             if (count > buffer.Length - index)
844             {
845                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
846                     new ArgumentOutOfRangeException("count",
847                     SR.GetString(SR.JsonSizeExceedsRemainingBufferSpace,
848                     buffer.Length - index)));
849             }
850 
851             WriteString(new string(buffer, index, count));
852         }
853 
854         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] // Microsoft, ToLowerInvariant is just used in Json error message
WriteStartAttribute(string prefix, string localName, string ns)855         public override void WriteStartAttribute(string prefix, string localName, string ns)
856         {
857             if (IsClosed)
858             {
859                 ThrowClosed();
860             }
861             if (!string.IsNullOrEmpty(prefix))
862             {
863                 if (IsWritingNameWithMapping && prefix == JsonGlobals.xmlnsPrefix)
864                 {
865                     if (ns != null && ns != xmlnsNamespace)
866                     {
867                         throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(System.Runtime.Serialization.SR.GetString(System.Runtime.Serialization.SR.XmlPrefixBoundToNamespace, "xmlns", xmlnsNamespace, ns), "ns"));
868                     }
869                 }
870                 else
871                 {
872                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("prefix", SR.GetString(SR.JsonPrefixMustBeNullOrEmpty, prefix));
873                 }
874             }
875             else
876             {
877                 if (IsWritingNameWithMapping && ns == xmlnsNamespace && localName != JsonGlobals.xmlnsPrefix)
878                 {
879                     prefix = JsonGlobals.xmlnsPrefix;
880                 }
881             }
882             if (!string.IsNullOrEmpty(ns))
883             {
884                 if (IsWritingNameWithMapping && ns == xmlnsNamespace)
885                 {
886                     prefix = JsonGlobals.xmlnsPrefix;
887                 }
888                 else if (string.IsNullOrEmpty(prefix) && localName == JsonGlobals.xmlnsPrefix && ns == xmlnsNamespace)
889                 {
890                     prefix = JsonGlobals.xmlnsPrefix;
891                     isWritingXmlnsAttributeDefaultNs = true;
892                 }
893                 else
894                 {
895                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("ns", SR.GetString(SR.JsonNamespaceMustBeEmpty, ns));
896                 }
897             }
898             if (localName == null)
899             {
900                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localName");
901             }
902             if (localName.Length == 0)
903             {
904                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("localName", SR.GetString(SR.JsonInvalidLocalNameEmpty));
905             }
906             if ((nodeType != JsonNodeType.Element) && !wroteServerTypeAttribute)
907             {
908                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.JsonAttributeMustHaveElement)));
909             }
910             if (HasOpenAttribute)
911             {
912                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
913                     new XmlException(SR.GetString(SR.JsonOpenAttributeMustBeClosedFirst, "WriteStartAttribute")));
914             }
915             if (prefix == JsonGlobals.xmlnsPrefix)
916             {
917                 isWritingXmlnsAttribute = true;
918             }
919             else if (localName == JsonGlobals.typeString)
920             {
921                 if (dataType != JsonDataType.None)
922                 {
923                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
924                         new XmlException(SR.GetString(SR.JsonAttributeAlreadyWritten, JsonGlobals.typeString)));
925                 }
926 
927                 isWritingDataTypeAttribute = true;
928             }
929             else if (localName == JsonGlobals.serverTypeString)
930             {
931                 if (serverTypeValue != null)
932                 {
933                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
934                         new XmlException(SR.GetString(SR.JsonAttributeAlreadyWritten, JsonGlobals.serverTypeString)));
935                 }
936 
937                 if ((dataType != JsonDataType.None) && (dataType != JsonDataType.Object))
938                 {
939                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
940                         new XmlException(SR.GetString(SR.JsonServerTypeSpecifiedForInvalidDataType,
941                         JsonGlobals.serverTypeString, JsonGlobals.typeString, dataType.ToString().ToLowerInvariant(), JsonGlobals.objectString)));
942                 }
943 
944                 isWritingServerTypeAttribute = true;
945             }
946             else if (localName == JsonGlobals.itemString)
947             {
948                 if (WrittenNameWithMapping)
949                 {
950                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
951                         new XmlException(SR.GetString(SR.JsonAttributeAlreadyWritten, JsonGlobals.itemString)));
952                 }
953 
954                 if (!IsWritingNameWithMapping)
955                 {
956                     // Don't write attribute with local name "item" if <item> element is not open.
957                     // Not providing a better error message because localization deadline has passed.
958                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
959                         new XmlException(SR.GetString(SR.JsonEndElementNoOpenNodes)));
960                 }
961 
962                 nameState |= NameState.IsWritingNameAttribute;
963             }
964             else
965             {
966                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("localName", SR.GetString(SR.JsonUnexpectedAttributeLocalName, localName));
967             }
968         }
969 
WriteStartDocument(bool standalone)970         public override void WriteStartDocument(bool standalone)
971         {
972             // In XML, writes the XML declaration with the version "1.0" and the standalone attribute.
973             WriteStartDocument();
974         }
975 
WriteStartDocument()976         public override void WriteStartDocument()
977         {
978             // In XML, writes the XML declaration with the version "1.0".
979             if (IsClosed)
980             {
981                 ThrowClosed();
982             }
983             if (WriteState != WriteState.Start)
984             {
985                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
986                     new XmlException(
987                     SR.GetString(SR.JsonInvalidWriteState, "WriteStartDocument", WriteState.ToString())));
988             }
989         }
990 
WriteStartElement(string prefix, string localName, string ns)991         public override void WriteStartElement(string prefix, string localName, string ns)
992         {
993             if (localName == null)
994             {
995                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localName");
996             }
997             if (localName.Length == 0)
998             {
999                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("localName",
1000                     SR.GetString(SR.JsonInvalidLocalNameEmpty));
1001             }
1002             if (!string.IsNullOrEmpty(prefix))
1003             {
1004                 if (string.IsNullOrEmpty(ns) || !TrySetWritingNameWithMapping(localName, ns))
1005                 {
1006                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("prefix", SR.GetString(SR.JsonPrefixMustBeNullOrEmpty, prefix));
1007                 }
1008             }
1009             if (!string.IsNullOrEmpty(ns))
1010             {
1011                 if (!TrySetWritingNameWithMapping(localName, ns))
1012                 {
1013                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("ns", SR.GetString(SR.JsonNamespaceMustBeEmpty, ns));
1014                 }
1015             }
1016             if (IsClosed)
1017             {
1018                 ThrowClosed();
1019             }
1020             if (HasOpenAttribute)
1021             {
1022                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1023                     new XmlException(SR.GetString(SR.JsonOpenAttributeMustBeClosedFirst, "WriteStartElement")));
1024             }
1025             if ((nodeType != JsonNodeType.None) && depth == 0)
1026             {
1027                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1028                     new XmlException(SR.GetString(SR.JsonMultipleRootElementsNotAllowedOnWriter)));
1029             }
1030 
1031             switch (nodeType)
1032             {
1033                 case JsonNodeType.None:
1034                     {
1035                         if (!localName.Equals(JsonGlobals.rootString))
1036                         {
1037                             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1038                                 new XmlException(SR.GetString(SR.JsonInvalidRootElementName, localName, JsonGlobals.rootString)));
1039                         }
1040                         EnterScope(JsonNodeType.Element);
1041                         break;
1042                     }
1043                 case JsonNodeType.Element:
1044                     {
1045                         if ((dataType != JsonDataType.Array) && (dataType != JsonDataType.Object))
1046                         {
1047                             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1048                                 new XmlException(SR.GetString(SR.JsonNodeTypeArrayOrObjectNotSpecified)));
1049                         }
1050                         if (indent)
1051                         {
1052                             WriteNewLine();
1053                             WriteIndent();
1054                         }
1055                         if (!IsWritingCollection)
1056                         {
1057                             if (nameState != NameState.IsWritingNameWithMapping)
1058                             {
1059                                 WriteJsonElementName(localName);
1060                             }
1061                         }
1062                         else if (!localName.Equals(JsonGlobals.itemString))
1063                         {
1064                             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1065                                 new XmlException(SR.GetString(SR.JsonInvalidItemNameForArrayElement, localName, JsonGlobals.itemString)));
1066                         }
1067                         EnterScope(JsonNodeType.Element);
1068                         break;
1069                     }
1070                 case JsonNodeType.EndElement:
1071                     {
1072                         if (endElementBuffer)
1073                         {
1074                             nodeWriter.WriteText(JsonGlobals.MemberSeparatorChar);
1075                         }
1076                         if (indent)
1077                         {
1078                             WriteNewLine();
1079                             WriteIndent();
1080                         }
1081                         if (!IsWritingCollection)
1082                         {
1083                             if (nameState != NameState.IsWritingNameWithMapping)
1084                             {
1085                                 WriteJsonElementName(localName);
1086                             }
1087                         }
1088                         else if (!localName.Equals(JsonGlobals.itemString))
1089                         {
1090                             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1091                                 new XmlException(SR.GetString(SR.JsonInvalidItemNameForArrayElement, localName, JsonGlobals.itemString)));
1092                         }
1093                         EnterScope(JsonNodeType.Element);
1094                         break;
1095                     }
1096                 default:
1097                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1098                         new XmlException(SR.GetString(SR.JsonInvalidStartElementCall)));
1099             }
1100 
1101             isWritingDataTypeAttribute = false;
1102             isWritingServerTypeAttribute = false;
1103             isWritingXmlnsAttribute = false;
1104             wroteServerTypeAttribute = false;
1105             serverTypeValue = null;
1106             dataType = JsonDataType.None;
1107             nodeType = JsonNodeType.Element;
1108         }
1109 
WriteString(string text)1110         public override void WriteString(string text)
1111         {
1112             if (HasOpenAttribute && (text != null))
1113             {
1114                 attributeText += text;
1115             }
1116             else
1117             {
1118                 if (text == null)
1119                 {
1120                     text = string.Empty;
1121                 }
1122 
1123                 // do work only when not indenting whitespaces
1124                 if (!((this.dataType == JsonDataType.Array || this.dataType == JsonDataType.Object || this.nodeType == JsonNodeType.EndElement) && XmlConverter.IsWhitespace(text)))
1125                 {
1126                     StartText();
1127                     WriteEscapedJsonString(text);
1128                 }
1129             }
1130         }
1131 
WriteSurrogateCharEntity(char lowChar, char highChar)1132         public override void WriteSurrogateCharEntity(char lowChar, char highChar)
1133         {
1134             WriteString(string.Concat(highChar, lowChar));
1135         }
1136 
WriteValue(bool value)1137         public override void WriteValue(bool value)
1138         {
1139             StartText();
1140             nodeWriter.WriteBoolText(value);
1141         }
1142 
WriteValue(decimal value)1143         public override void WriteValue(decimal value)
1144         {
1145             StartText();
1146             nodeWriter.WriteDecimalText(value);
1147         }
1148 
WriteValue(double value)1149         public override void WriteValue(double value)
1150         {
1151             StartText();
1152             nodeWriter.WriteDoubleText(value);
1153         }
1154 
WriteValue(float value)1155         public override void WriteValue(float value)
1156         {
1157             StartText();
1158             nodeWriter.WriteFloatText(value);
1159         }
1160 
WriteValue(int value)1161         public override void WriteValue(int value)
1162         {
1163             StartText();
1164             nodeWriter.WriteInt32Text(value);
1165         }
1166 
WriteValue(long value)1167         public override void WriteValue(long value)
1168         {
1169             StartText();
1170             nodeWriter.WriteInt64Text(value);
1171         }
1172 
WriteValue(Guid value)1173         public override void WriteValue(Guid value)
1174         {
1175             StartText();
1176             nodeWriter.WriteGuidText(value);
1177         }
1178 
WriteValue(DateTime value)1179         public override void WriteValue(DateTime value)
1180         {
1181             StartText();
1182             nodeWriter.WriteDateTimeText(value);
1183         }
1184 
WriteValue(string value)1185         public override void WriteValue(string value)
1186         {
1187             WriteString(value);
1188         }
1189 
WriteValue(TimeSpan value)1190         public override void WriteValue(TimeSpan value)
1191         {
1192             StartText();
1193             nodeWriter.WriteTimeSpanText(value);
1194         }
1195 
WriteValue(UniqueId value)1196         public override void WriteValue(UniqueId value)
1197         {
1198             if (value == null)
1199             {
1200                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
1201             }
1202 
1203             StartText();
1204             nodeWriter.WriteUniqueIdText(value);
1205         }
1206 
WriteValue(object value)1207         public override void WriteValue(object value)
1208         {
1209             if (IsClosed)
1210             {
1211                 ThrowClosed();
1212             }
1213 
1214             if (value == null)
1215             {
1216                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
1217             }
1218 
1219             if (value is Array)
1220             {
1221                 WriteValue((Array)value);
1222             }
1223             else if (value is IStreamProvider)
1224             {
1225                 WriteValue((IStreamProvider)value);
1226             }
1227             else
1228             {
1229                 WritePrimitiveValue(value);
1230             }
1231         }
1232 
1233         [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", Justification = "This method is derived from the base")]
WriteWhitespace(string ws)1234         public override void WriteWhitespace(string ws)
1235         {
1236             if (IsClosed)
1237             {
1238                 ThrowClosed();
1239             }
1240             if (ws == null)
1241             {
1242                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("ws");
1243             }
1244 
1245             for (int i = 0; i < ws.Length; ++i)
1246             {
1247                 char c = ws[i];
1248                 if (c != ' ' &&
1249                     c != '\t' &&
1250                     c != '\n' &&
1251                     c != '\r')
1252                 {
1253                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("ws",
1254                         SR.GetString(SR.JsonOnlyWhitespace, c.ToString(), "WriteWhitespace"));
1255                 }
1256             }
1257 
1258             WriteString(ws);
1259         }
1260 
WriteXmlAttribute(string localName, string value)1261         public override void WriteXmlAttribute(string localName, string value)
1262         {
1263             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteXmlAttribute")));
1264         }
1265 
WriteXmlAttribute(XmlDictionaryString localName, XmlDictionaryString value)1266         public override void WriteXmlAttribute(XmlDictionaryString localName, XmlDictionaryString value)
1267         {
1268             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteXmlAttribute")));
1269         }
1270 
WriteXmlnsAttribute(string prefix, string namespaceUri)1271         public override void WriteXmlnsAttribute(string prefix, string namespaceUri)
1272         {
1273             if (!IsWritingNameWithMapping)
1274             {
1275                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteXmlnsAttribute")));
1276             }
1277         }
1278 
WriteXmlnsAttribute(string prefix, XmlDictionaryString namespaceUri)1279         public override void WriteXmlnsAttribute(string prefix, XmlDictionaryString namespaceUri)
1280         {
1281             if (!IsWritingNameWithMapping)
1282             {
1283                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.JsonMethodNotSupported, "WriteXmlnsAttribute")));
1284             }
1285         }
1286 
CharacterNeedsEscaping(char ch)1287         internal static bool CharacterNeedsEscaping(char ch)
1288         {
1289             return (ch == FORWARD_SLASH || ch == JsonGlobals.QuoteChar || ch < WHITESPACE || ch == BACK_SLASH
1290                 || (ch >= HIGH_SURROGATE_START && (ch <= LOW_SURROGATE_END || ch >= MAX_CHAR)));
1291         }
1292 
1293 
ThrowClosed()1294         static void ThrowClosed()
1295         {
1296             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1297                 new InvalidOperationException(SR.GetString(SR.JsonWriterClosed)));
1298         }
1299 
CheckText(JsonNodeType nextNodeType)1300         void CheckText(JsonNodeType nextNodeType)
1301         {
1302             if (IsClosed)
1303             {
1304                 ThrowClosed();
1305             }
1306             if (depth == 0)
1307             {
1308                 throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1309                     new InvalidOperationException(
1310                     System.Runtime.Serialization.SR.GetString(System.Runtime.Serialization.SR.XmlIllegalOutsideRoot)));
1311             }
1312 
1313             if ((nextNodeType == JsonNodeType.StandaloneText) &&
1314                 (nodeType == JsonNodeType.QuotedText))
1315             {
1316                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1317                     new XmlException(
1318                     SR.GetString(SR.JsonCannotWriteStandaloneTextAfterQuotedText)));
1319             }
1320         }
1321 
EnterScope(JsonNodeType currentNodeType)1322         void EnterScope(JsonNodeType currentNodeType)
1323         {
1324             depth++;
1325             if (scopes == null)
1326             {
1327                 scopes = new JsonNodeType[4];
1328             }
1329             else if (scopes.Length == depth)
1330             {
1331                 JsonNodeType[] newScopes = new JsonNodeType[depth * 2];
1332                 Array.Copy(scopes, newScopes, depth);
1333                 scopes = newScopes;
1334             }
1335             scopes[depth] = currentNodeType;
1336         }
1337 
ExitScope()1338         JsonNodeType ExitScope()
1339         {
1340             JsonNodeType nodeTypeToReturn = scopes[depth];
1341             scopes[depth] = JsonNodeType.None;
1342             depth--;
1343             return nodeTypeToReturn;
1344         }
1345 
InitializeWriter()1346         void InitializeWriter()
1347         {
1348             nodeType = JsonNodeType.None;
1349             dataType = JsonDataType.None;
1350             isWritingDataTypeAttribute = false;
1351             wroteServerTypeAttribute = false;
1352             isWritingServerTypeAttribute = false;
1353             serverTypeValue = null;
1354             attributeText = null;
1355 
1356             if (depth != 0)
1357             {
1358                 depth = 0;
1359             }
1360             if ((scopes != null) && (scopes.Length > JsonGlobals.maxScopeSize))
1361             {
1362                 scopes = null;
1363             }
1364 
1365             // Can't let writeState be at Closed if reinitializing.
1366             writeState = WriteState.Start;
1367             endElementBuffer = false;
1368             indentLevel = 0;
1369         }
1370 
IsUnicodeNewlineCharacter(char c)1371         static bool IsUnicodeNewlineCharacter(char c)
1372         {
1373             // Newline characters in JSON strings need to be encoded on the way out (DevDiv #665974)
1374             // See Unicode 6.2, Table 5-1 (http://www.unicode.org/versions/Unicode6.2.0/ch05.pdf]) for the full list.
1375 
1376             // We only care about NEL, LS, and PS, since the other newline characters are all
1377             // control characters so are already encoded.
1378             return (c == '\u0085' || c == '\u2028' || c == '\u2029');
1379         }
1380 
StartText()1381         void StartText()
1382         {
1383             if (HasOpenAttribute)
1384             {
1385                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.JsonMustUseWriteStringForWritingAttributeValues)));
1386             }
1387 
1388             if ((dataType == JsonDataType.None) && (serverTypeValue != null))
1389             {
1390                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1391                     new XmlException(SR.GetString(SR.JsonMustSpecifyDataType,
1392                     JsonGlobals.typeString, JsonGlobals.objectString, JsonGlobals.serverTypeString)));
1393             }
1394 
1395             if (IsWritingNameWithMapping && !WrittenNameWithMapping)
1396             {
1397                 // Don't write out any text content unless the local name has been written.
1398                 // Not providing a better error message because localization deadline has passed.
1399                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1400                     new XmlException(SR.GetString(SR.JsonMustSpecifyDataType,
1401                     JsonGlobals.itemString, string.Empty, JsonGlobals.itemString)));
1402             }
1403 
1404             if ((dataType == JsonDataType.String) ||
1405                 (dataType == JsonDataType.None))
1406             {
1407                 CheckText(JsonNodeType.QuotedText);
1408                 if (nodeType != JsonNodeType.QuotedText)
1409                 {
1410                     WriteJsonQuote();
1411                 }
1412                 nodeType = JsonNodeType.QuotedText;
1413             }
1414             else if ((dataType == JsonDataType.Number) ||
1415                 (dataType == JsonDataType.Boolean))
1416             {
1417                 CheckText(JsonNodeType.StandaloneText);
1418                 nodeType = JsonNodeType.StandaloneText;
1419             }
1420             else
1421             {
1422                 ThrowInvalidAttributeContent();
1423             }
1424         }
1425 
ThrowIfServerTypeWritten(string dataTypeSpecified)1426         void ThrowIfServerTypeWritten(string dataTypeSpecified)
1427         {
1428             if (serverTypeValue != null)
1429             {
1430                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1431                     new XmlException(SR.GetString(SR.JsonInvalidDataTypeSpecifiedForServerType,
1432                     JsonGlobals.typeString, dataTypeSpecified, JsonGlobals.serverTypeString, JsonGlobals.objectString)));
1433             }
1434         }
1435 
1436         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] // Microsoft, ToLowerInvariant is just used in Json error message
ThrowInvalidAttributeContent()1437         void ThrowInvalidAttributeContent()
1438         {
1439             if (HasOpenAttribute)
1440             {
1441                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1442                     new XmlException(SR.GetString(SR.JsonInvalidMethodBetweenStartEndAttribute)));
1443             }
1444             else
1445             {
1446                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
1447                     new XmlException(SR.GetString(SR.JsonCannotWriteTextAfterNonTextAttribute,
1448                     dataType.ToString().ToLowerInvariant())));
1449             }
1450         }
1451 
TrySetWritingNameWithMapping(string localName, string ns)1452         bool TrySetWritingNameWithMapping(string localName, string ns)
1453         {
1454             if (localName.Equals(JsonGlobals.itemString) && ns.Equals(JsonGlobals.itemString))
1455             {
1456                 nameState = NameState.IsWritingNameWithMapping;
1457                 return true;
1458             }
1459             return false;
1460         }
1461 
WriteDataTypeServerType()1462         void WriteDataTypeServerType()
1463         {
1464             if (dataType != JsonDataType.None)
1465             {
1466                 switch (dataType)
1467                 {
1468                     case JsonDataType.Array:
1469                         {
1470                             EnterScope(JsonNodeType.Collection);
1471                             nodeWriter.WriteText(JsonGlobals.CollectionChar);
1472                             indentLevel++;
1473                             break;
1474                         }
1475                     case JsonDataType.Object:
1476                         {
1477                             EnterScope(JsonNodeType.Object);
1478                             nodeWriter.WriteText(JsonGlobals.ObjectChar);
1479                             indentLevel++;
1480                             break;
1481                         }
1482                     case JsonDataType.Null:
1483                         {
1484                             nodeWriter.WriteText(JsonGlobals.nullString);
1485                             break;
1486                         }
1487                     default:
1488                         break;
1489                 }
1490 
1491                 if (serverTypeValue != null)
1492                 {
1493                     // dataType must be object because we throw in all other case.
1494                     WriteServerTypeAttribute();
1495                 }
1496             }
1497         }
1498 
1499         [SecuritySafeCritical]
WriteEscapedJsonString(string str)1500         unsafe void WriteEscapedJsonString(string str)
1501         {
1502             fixed (char* chars = str)
1503             {
1504                 int i = 0;
1505                 int j;
1506                 for (j = 0; j < str.Length; j++)
1507                 {
1508                     char ch = chars[j];
1509                     if (ch <= FORWARD_SLASH)
1510                     {
1511                         if (ch == FORWARD_SLASH || ch == JsonGlobals.QuoteChar)
1512                         {
1513                             nodeWriter.WriteChars(chars + i, j - i);
1514                             nodeWriter.WriteText(BACK_SLASH);
1515                             nodeWriter.WriteText(ch);
1516                             i = j + 1;
1517                         }
1518                         else if (ch < WHITESPACE)
1519                         {
1520                             nodeWriter.WriteChars(chars + i, j - i);
1521                             nodeWriter.WriteText(BACK_SLASH);
1522                             if (CharacterAbbrevs[ch] == 0)
1523                             {
1524                                 nodeWriter.WriteText('u');
1525                                 nodeWriter.WriteText(string.Format(CultureInfo.InvariantCulture, "{0:x4}", (int)ch));
1526                                 i = j + 1;
1527                             }
1528                             else
1529                             {
1530                                 nodeWriter.WriteText(CharacterAbbrevs[ch]);
1531                                 i = j + 1;
1532                             }
1533                         }
1534                     }
1535                     else if (ch == BACK_SLASH)
1536                     {
1537                         nodeWriter.WriteChars(chars + i, j - i);
1538                         nodeWriter.WriteText(BACK_SLASH);
1539                         nodeWriter.WriteText(ch);
1540                         i = j + 1;
1541                     }
1542                     else if ((ch >= HIGH_SURROGATE_START && (ch <= LOW_SURROGATE_END || ch >= MAX_CHAR)) || IsUnicodeNewlineCharacter(ch))
1543                     {
1544                         nodeWriter.WriteChars(chars + i, j - i);
1545                         nodeWriter.WriteText(BACK_SLASH);
1546                         nodeWriter.WriteText('u');
1547                         nodeWriter.WriteText(string.Format(CultureInfo.InvariantCulture, "{0:x4}", (int)ch));
1548                         i = j + 1;
1549                     }
1550                 }
1551                 if (i < j)
1552                 {
1553                     nodeWriter.WriteChars(chars + i, j - i);
1554                 }
1555             }
1556         }
1557 
WriteIndent()1558         void WriteIndent()
1559         {
1560             for (int i = 0; i < indentLevel; i++)
1561             {
1562                 nodeWriter.WriteText(indentChars);
1563             }
1564         }
1565 
WriteNewLine()1566         void WriteNewLine()
1567         {
1568             nodeWriter.WriteText(CARRIAGE_RETURN);
1569             nodeWriter.WriteText(NEWLINE);
1570         }
1571 
WriteJsonElementName(string localName)1572         void WriteJsonElementName(string localName)
1573         {
1574             WriteJsonQuote();
1575             WriteEscapedJsonString(localName);
1576             WriteJsonQuote();
1577             nodeWriter.WriteText(JsonGlobals.NameValueSeparatorChar);
1578             if (indent)
1579             {
1580                 nodeWriter.WriteText(WHITESPACE);
1581             }
1582         }
1583 
WriteJsonQuote()1584         void WriteJsonQuote()
1585         {
1586             nodeWriter.WriteText(JsonGlobals.QuoteChar);
1587         }
1588 
WritePrimitiveValue(object value)1589         void WritePrimitiveValue(object value)
1590         {
1591             if (IsClosed)
1592             {
1593                 ThrowClosed();
1594             }
1595 
1596             if (value == null)
1597             {
1598                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("value"));
1599             }
1600 
1601             if (value is ulong)
1602             {
1603                 WriteValue((ulong)value);
1604             }
1605             else if (value is string)
1606             {
1607                 WriteValue((string)value);
1608             }
1609             else if (value is int)
1610             {
1611                 WriteValue((int)value);
1612             }
1613             else if (value is long)
1614             {
1615                 WriteValue((long)value);
1616             }
1617             else if (value is bool)
1618             {
1619                 WriteValue((bool)value);
1620             }
1621             else if (value is double)
1622             {
1623                 WriteValue((double)value);
1624             }
1625             else if (value is DateTime)
1626             {
1627                 WriteValue((DateTime)value);
1628             }
1629             else if (value is float)
1630             {
1631                 WriteValue((float)value);
1632             }
1633             else if (value is decimal)
1634             {
1635                 WriteValue((decimal)value);
1636             }
1637             else if (value is XmlDictionaryString)
1638             {
1639                 WriteValue((XmlDictionaryString)value);
1640             }
1641             else if (value is UniqueId)
1642             {
1643                 WriteValue((UniqueId)value);
1644             }
1645             else if (value is Guid)
1646             {
1647                 WriteValue((Guid)value);
1648             }
1649             else if (value is TimeSpan)
1650             {
1651                 WriteValue((TimeSpan)value);
1652             }
1653             else if (value.GetType().IsArray)
1654             {
1655                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.JsonNestedArraysNotSupported), "value"));
1656             }
1657             else
1658             {
1659                 base.WriteValue(value);
1660             }
1661         }
1662 
WriteServerTypeAttribute()1663         void WriteServerTypeAttribute()
1664         {
1665             string value = serverTypeValue;
1666             JsonDataType oldDataType = dataType;
1667             NameState oldNameState = nameState;
1668             WriteStartElement(JsonGlobals.serverTypeString);
1669             WriteValue(value);
1670             WriteEndElement();
1671             dataType = oldDataType;
1672             nameState = oldNameState;
1673             wroteServerTypeAttribute = true;
1674         }
1675 
WriteValue(ulong value)1676         void WriteValue(ulong value)
1677         {
1678             StartText();
1679             nodeWriter.WriteUInt64Text(value);
1680         }
1681 
WriteValue(Array array)1682         void WriteValue(Array array)
1683         {
1684             // This method is called only if WriteValue(object) is called with an array
1685             // The contract for XmlWriter.WriteValue(object) requires that this object array be written out as a string.
1686             // E.g. WriteValue(new int[] { 1, 2, 3}) should be equivalent to WriteString("1 2 3").
1687             JsonDataType oldDataType = dataType;
1688             // Set attribute mode to String because WritePrimitiveValue might write numerical text.
1689             //  Calls to methods that write numbers can't be mixed with calls that write quoted text unless the attribute mode is explictly string.
1690             dataType = JsonDataType.String;
1691             StartText();
1692             for (int i = 0; i < array.Length; i++)
1693             {
1694                 if (i != 0)
1695                 {
1696                     nodeWriter.WriteText(JsonGlobals.WhitespaceChar);
1697                 }
1698                 WritePrimitiveValue(array.GetValue(i));
1699             }
1700             dataType = oldDataType;
1701         }
1702 
1703         class JsonNodeWriter : XmlUTF8NodeWriter
1704         {
1705             [SecurityCritical]
WriteChars(char* chars, int charCount)1706             internal unsafe void WriteChars(char* chars, int charCount)
1707             {
1708                 base.UnsafeWriteUTF8Chars(chars, charCount);
1709             }
1710         }
1711     }
1712 }
1713