Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Runtime.Serialization / System / Runtime / Serialization / Json / JsonEncodingStreamWrapper.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4
5 #pragma warning disable 1634 // Stops compiler from warning about unknown warnings (for Presharp)
6
7 namespace System.Runtime.Serialization.Json
8 {
9     using System.IO;
10 #if !MONO
11     using System.ServiceModel;
12 #endif
13     using System.Text;
14     using System.Xml;
15     using System.Security;
16
17     // This wrapper does not support seek.
18     // Supports: UTF-8, Unicode, BigEndianUnicode
19     // ASSUMPTION (Microsoft): This class will only be used for EITHER reading OR writing.  It can be done, it would just mean more buffers.
20     class JsonEncodingStreamWrapper : Stream
21     {
22         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Static fields are marked SecurityCritical or readonly to prevent"
23             + " data from being modified or leaked to other components in appdomain.")]
24         static readonly UnicodeEncoding SafeBEUTF16 = new UnicodeEncoding(true, false, false);
25
26         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Static fields are marked SecurityCritical or readonly to prevent"
27             + " data from being modified or leaked to other components in appdomain.")]
28         static readonly UnicodeEncoding SafeUTF16 = new UnicodeEncoding(false, false, false);
29
30         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Static fields are marked SecurityCritical or readonly to prevent"
31             + " data from being modified or leaked to other components in appdomain.")]
32         static readonly UTF8Encoding SafeUTF8 = new UTF8Encoding(false, false);
33
34         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Static fields are marked SecurityCritical or readonly to prevent"
35             + " data from being modified or leaked to other components in appdomain.")]
36         static readonly UnicodeEncoding ValidatingBEUTF16 = new UnicodeEncoding(true, false, true);
37
38         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Static fields are marked SecurityCritical or readonly to prevent"
39             + " data from being modified or leaked to other components in appdomain.")]
40         static readonly UnicodeEncoding ValidatingUTF16 = new UnicodeEncoding(false, false, true);
41
42         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - Static fields are marked SecurityCritical or readonly to prevent"
43             + " data from being modified or leaked to other components in appdomain.")]
44         static readonly UTF8Encoding ValidatingUTF8 = new UTF8Encoding(false, true);
45         const int BufferLength = 128;
46
47         byte[] byteBuffer = new byte[1];
48         int byteCount;
49         int byteOffset;
50         byte[] bytes;
51         char[] chars;
52         Decoder dec;
53         Encoder enc;
54         Encoding encoding;
55
56         SupportedEncoding encodingCode;
57         bool isReading;
58
59         Stream stream;
60
61         public JsonEncodingStreamWrapper(Stream stream, Encoding encoding, bool isReader)
62         {
63             this.isReading = isReader;
64             if (isReader)
65             {
66                 InitForReading(stream, encoding);
67             }
68             else
69             {
70                 if (encoding == null)
71                 {
72                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encoding");
73                 }
74
75                 InitForWriting(stream, encoding);
76             }
77         }
78
79         enum SupportedEncoding
80         {
81             UTF8,
82             UTF16LE,
83             UTF16BE,
84             None
85         }
86
87         // This stream wrapper does not support duplex
88         public override bool CanRead
89         {
90             get
91             {
92                 if (!isReading)
93                 {
94                     return false;
95                 }
96
97                 return this.stream.CanRead;
98             }
99         }
100
101         // The encoding conversion and buffering breaks seeking.
102         public override bool CanSeek
103         {
104             get { return false; }
105         }
106
107         // Delegate properties
108         public override bool CanTimeout
109         {
110             get { return this.stream.CanTimeout; }
111         }
112
113         // This stream wrapper does not support duplex
114         public override bool CanWrite
115         {
116             get
117             {
118                 if (isReading)
119                 {
120                     return false;
121                 }
122
123                 return this.stream.CanWrite;
124             }
125         }
126
127         public override long Length
128         {
129             get { return this.stream.Length; }
130         }
131
132
133         // The encoding conversion and buffering breaks seeking.
134         public override long Position
135         {
136             get
137             {
138 #pragma warning suppress 56503 // The contract for non seekable stream is to throw exception
139                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
140             }
141             set { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); }
142         }
143
144         public override int ReadTimeout
145         {
146             get { return this.stream.ReadTimeout; }
147             set { this.stream.ReadTimeout = value; }
148         }
149
150         public override int WriteTimeout
151         {
152             get { return this.stream.WriteTimeout; }
153             set { this.stream.WriteTimeout = value; }
154         }
155
156         public static ArraySegment<byte> ProcessBuffer(byte[] buffer, int offset, int count, Encoding encoding)
157         {
158             try
159             {
160                 SupportedEncoding expectedEnc = GetSupportedEncoding(encoding);
161                 SupportedEncoding dataEnc;
162                 if (count < 2)
163                 {
164                     dataEnc = SupportedEncoding.UTF8;
165                 }
166                 else
167                 {
168                     dataEnc = ReadEncoding(buffer[offset], buffer[offset + 1]);
169                 }
170                 if ((expectedEnc != SupportedEncoding.None) && (expectedEnc != dataEnc))
171                 {
172                     ThrowExpectedEncodingMismatch(expectedEnc, dataEnc);
173                 }
174
175                 // Fastpath: UTF-8
176                 if (dataEnc == SupportedEncoding.UTF8)
177                 {
178                     return new ArraySegment<byte>(buffer, offset, count);
179                 }
180
181                 // Convert to UTF-8
182                 return
183                     new ArraySegment<byte>(ValidatingUTF8.GetBytes(GetEncoding(dataEnc).GetChars(buffer, offset, count)));
184             }
185             catch (DecoderFallbackException e)
186             {
187                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
188                     new XmlException(SR.GetString(SR.JsonInvalidBytes), e));
189             }
190         }
191
192         public override void Close()
193         {
194             Flush();
195             base.Close();
196             this.stream.Close();
197         }
198
199         public override void Flush()
200         {
201             this.stream.Flush();
202         }
203
204         public override int Read(byte[] buffer, int offset, int count)
205         {
206             try
207             {
208                 if (byteCount == 0)
209                 {
210                     if (encodingCode == SupportedEncoding.UTF8)
211                     {
212                         return this.stream.Read(buffer, offset, count);
213                     }
214
215                     // No more bytes than can be turned into characters
216                     byteOffset = 0;
217                     byteCount = this.stream.Read(bytes, byteCount, (chars.Length - 1) * 2);
218
219                     // Check for end of stream
220                     if (byteCount == 0)
221                     {
222                         return 0;
223                     }
224
225                     // Fix up incomplete chars
226                     CleanupCharBreak();
227
228                     // Change encoding
229                     int charCount = this.encoding.GetChars(bytes, 0, byteCount, chars, 0);
230                     byteCount = Encoding.UTF8.GetBytes(chars, 0, charCount, bytes, 0);
231                 }
232
233                 // Give them bytes
234                 if (byteCount < count)
235                 {
236                     count = byteCount;
237                 }
238                 Buffer.BlockCopy(bytes, byteOffset, buffer, offset, count);
239                 byteOffset += count;
240                 byteCount -= count;
241                 return count;
242             }
243             catch (DecoderFallbackException ex)
244             {
245                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
246                     new XmlException(SR.GetString(SR.JsonInvalidBytes), ex));
247             }
248         }
249
250         public override int ReadByte()
251         {
252             if (byteCount == 0 && encodingCode == SupportedEncoding.UTF8)
253             {
254                 return this.stream.ReadByte();
255             }
256             if (Read(byteBuffer, 0, 1) == 0)
257             {
258                 return -1;
259             }
260             return byteBuffer[0];
261         }
262
263         public override long Seek(long offset, SeekOrigin origin)
264         {
265             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
266         }
267
268         // Delegate methods
269         public override void SetLength(long value)
270         {
271             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
272         }
273
274         public override void Write(byte[] buffer, int offset, int count)
275         {
276             // Optimize UTF-8 case
277             if (encodingCode == SupportedEncoding.UTF8)
278             {
279                 this.stream.Write(buffer, offset, count);
280                 return;
281             }
282
283             while (count > 0)
284             {
285                 int size = chars.Length < count ? chars.Length : count;
286                 int charCount = dec.GetChars(buffer, offset, size, chars, 0, false);
287                 byteCount = enc.GetBytes(chars, 0, charCount, bytes, 0, false);
288                 this.stream.Write(bytes, 0, byteCount);
289                 offset += size;
290                 count -= size;
291             }
292         }
293
294         public override void WriteByte(byte b)
295         {
296             if (encodingCode == SupportedEncoding.UTF8)
297             {
298                 this.stream.WriteByte(b);
299                 return;
300             }
301             byteBuffer[0] = b;
302             Write(byteBuffer, 0, 1);
303         }
304
305         static Encoding GetEncoding(SupportedEncoding e)
306         {
307             switch (e)
308             {
309                 case SupportedEncoding.UTF8:
310                     return ValidatingUTF8;
311
312                 case SupportedEncoding.UTF16LE:
313                     return ValidatingUTF16;
314
315                 case SupportedEncoding.UTF16BE:
316                     return ValidatingBEUTF16;
317
318                 default:
319                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
320                         new XmlException(SR.GetString(SR.JsonEncodingNotSupported)));
321             }
322         }
323
324         static string GetEncodingName(SupportedEncoding enc)
325         {
326             switch (enc)
327             {
328                 case SupportedEncoding.UTF8:
329                     return "utf-8";
330
331                 case SupportedEncoding.UTF16LE:
332                     return "utf-16LE";
333
334                 case SupportedEncoding.UTF16BE:
335                     return "utf-16BE";
336
337                 default:
338                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
339                         new XmlException(SR.GetString(SR.JsonEncodingNotSupported)));
340             }
341         }
342
343         static SupportedEncoding GetSupportedEncoding(Encoding encoding)
344         {
345             if (encoding == null)
346             {
347                 return SupportedEncoding.None;
348             }
349             if (encoding.WebName == ValidatingUTF8.WebName)
350             {
351                 return SupportedEncoding.UTF8;
352             }
353             else if (encoding.WebName == ValidatingUTF16.WebName)
354             {
355                 return SupportedEncoding.UTF16LE;
356             }
357             else if (encoding.WebName == ValidatingBEUTF16.WebName)
358             {
359                 return SupportedEncoding.UTF16BE;
360             }
361             else
362             {
363                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
364                     new XmlException(SR.GetString(SR.JsonEncodingNotSupported)));
365             }
366         }
367
368         static SupportedEncoding ReadEncoding(byte b1, byte b2)
369         {
370             if (b1 == 0x00 && b2 != 0x00)
371             {
372                 return SupportedEncoding.UTF16BE;
373             }
374             else if (b1 != 0x00 && b2 == 0x00)
375             {
376                 // 857 It's possible to misdetect UTF-32LE as UTF-16LE, but that's OK.
377                 return SupportedEncoding.UTF16LE;
378             }
379             else if (b1 == 0x00 && b2 == 0x00)
380             {
381                 // UTF-32BE not supported
382                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.JsonInvalidBytes)));
383             }
384             else
385             {
386                 return SupportedEncoding.UTF8;
387             }
388         }
389
390         static void ThrowExpectedEncodingMismatch(SupportedEncoding expEnc, SupportedEncoding actualEnc)
391         {
392             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.JsonExpectedEncoding, GetEncodingName(expEnc), GetEncodingName(actualEnc))));
393         }
394
395         void CleanupCharBreak()
396         {
397             int max = byteOffset + byteCount;
398
399             // Read on 2 byte boundaries
400             if ((byteCount % 2) != 0)
401             {
402                 int b = this.stream.ReadByte();
403                 if (b < 0)
404                 {
405                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
406                         new XmlException(SR.GetString(SR.JsonUnexpectedEndOfFile)));
407                 }
408
409                 bytes[max++] = (byte)b;
410                 byteCount++;
411             }
412
413             // Don't cut off a surrogate character
414             int w;
415             if (encodingCode == SupportedEncoding.UTF16LE)
416             {
417                 w = bytes[max - 2] + (bytes[max - 1] << 8);
418             }
419             else
420             {
421                 w = bytes[max - 1] + (bytes[max - 2] << 8);
422             }
423             if ((w & 0xDC00) != 0xDC00 && w >= 0xD800 && w <= 0xDBFF) // First 16-bit number of surrogate pair
424             {
425                 int b1 = this.stream.ReadByte();
426                 int b2 = this.stream.ReadByte();
427                 if (b2 < 0)
428                 {
429                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
430                         new XmlException(SR.GetString(SR.JsonUnexpectedEndOfFile)));
431                 }
432                 bytes[max++] = (byte)b1;
433                 bytes[max++] = (byte)b2;
434                 byteCount += 2;
435             }
436         }
437
438         void EnsureBuffers()
439         {
440             EnsureByteBuffer();
441             if (chars == null)
442             {
443                 chars = new char[BufferLength];
444             }
445         }
446
447         void EnsureByteBuffer()
448         {
449             if (bytes != null)
450             {
451                 return;
452             }
453
454             bytes = new byte[BufferLength * 4];
455             byteOffset = 0;
456             byteCount = 0;
457         }
458
459         void FillBuffer(int count)
460         {
461             count -= byteCount;
462             while (count > 0)
463             {
464                 int read = stream.Read(bytes, byteOffset + byteCount, count);
465                 if (read == 0)
466                 {
467                     break;
468                 }
469
470                 byteCount += read;
471                 count -= read;
472             }
473         }
474
475         void InitForReading(Stream inputStream, Encoding expectedEncoding)
476         {
477             try
478             {
479                 this.stream = new BufferedStream(inputStream);
480
481                 SupportedEncoding expectedEnc = GetSupportedEncoding(expectedEncoding);
482                 SupportedEncoding dataEnc = ReadEncoding();
483                 if ((expectedEnc != SupportedEncoding.None) && (expectedEnc != dataEnc))
484                 {
485                     ThrowExpectedEncodingMismatch(expectedEnc, dataEnc);
486                 }
487
488                 // Fastpath: UTF-8 (do nothing)
489                 if (dataEnc != SupportedEncoding.UTF8)
490                 {
491                     // Convert to UTF-8
492                     EnsureBuffers();
493                     FillBuffer((BufferLength - 1) * 2);
494                     this.encodingCode = dataEnc;
495                     this.encoding = GetEncoding(dataEnc);
496                     CleanupCharBreak();
497                     int count = this.encoding.GetChars(bytes, byteOffset, byteCount, chars, 0);
498                     byteOffset = 0;
499                     byteCount = ValidatingUTF8.GetBytes(chars, 0, count, bytes, 0);
500                 }
501             }
502             catch (DecoderFallbackException ex)
503             {
504                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
505                     new XmlException(SR.GetString(SR.JsonInvalidBytes), ex));
506             }
507         }
508
509         void InitForWriting(Stream outputStream, Encoding writeEncoding)
510         {
511             this.encoding = writeEncoding;
512             this.stream = new BufferedStream(outputStream);
513
514             // Set the encoding code
515             this.encodingCode = GetSupportedEncoding(writeEncoding);
516
517             if (this.encodingCode != SupportedEncoding.UTF8)
518             {
519                 EnsureBuffers();
520                 dec = ValidatingUTF8.GetDecoder();
521                 enc = this.encoding.GetEncoder();
522             }
523         }
524
525         SupportedEncoding ReadEncoding()
526         {
527             int b1 = this.stream.ReadByte();
528             int b2 = this.stream.ReadByte();
529
530             EnsureByteBuffer();
531
532             SupportedEncoding e;
533
534             if (b1 == -1)
535             {
536                 e = SupportedEncoding.UTF8;
537                 byteCount = 0;
538             }
539             else if (b2 == -1)
540             {
541                 e = SupportedEncoding.UTF8;
542                 bytes[0] = (byte)b1;
543                 byteCount = 1;
544             }
545             else
546             {
547                 e = ReadEncoding((byte)b1, (byte)b2);
548                 bytes[0] = (byte)b1;
549                 bytes[1] = (byte)b2;
550                 byteCount = 2;
551             }
552
553             return e;
554         }
555     }
556 }