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