Merge pull request #260 from pcc/topmost
[mono.git] / mcs / class / corlib / System.IO / StreamReader.cs
1 //
2 // System.IO.StreamReader.cs
3 //
4 // Authors:
5 //   Dietmar Maurer (dietmar@ximian.com)
6 //   Miguel de Icaza (miguel@ximian.com) 
7 //   Marek Safar (marek.safar@gmail.com)
8 //
9 // (C) Ximian, Inc.  http://www.ximian.com
10 // Copyright (C) 2004 Novell (http://www.novell.com)
11 // Copyright 2011, 2013 Xamarin Inc.
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Text;
35 using System.Runtime.InteropServices;
36 #if NET_4_5
37 using System.Threading.Tasks;
38 #endif
39
40 namespace System.IO {
41         [Serializable]
42         [ComVisible (true)]
43         public class StreamReader : TextReader
44         {
45                 sealed class NullStreamReader : StreamReader
46                 {
47                         public override int Peek ()
48                         {
49                                 return -1;
50                         }
51
52                         public override int Read ()
53                         {
54                                 return -1;
55                         }
56
57                         public override int Read ([In, Out] char[] buffer, int index, int count)
58                         {
59                                 return 0;
60                         }
61
62                         public override string ReadLine ()
63                         {
64                                 return null;
65                         }
66
67                         public override string ReadToEnd ()
68                         {
69                                 return String.Empty;
70                         }
71
72                         public override Stream BaseStream {
73                                 get { return Stream.Null; }
74                         }
75
76                         public override Encoding CurrentEncoding {
77                                 get { return Encoding.Unicode; }
78                         }
79                 }
80
81                 const int DefaultBufferSize = 1024;
82                 const int DefaultFileBufferSize = 4096;
83                 const int MinimumBufferSize = 128;
84
85                 //
86                 // The input buffer
87                 //
88                 byte [] input_buffer;
89                 
90                 // Input buffer ready for recycling
91                 static byte [] input_buffer_recycle;
92                 static object input_buffer_recycle_lock = new object ();
93
94                 //
95                 // The decoded buffer from the above input buffer
96                 //
97                 char [] decoded_buffer;
98                 static char[] decoded_buffer_recycle;
99
100                 Encoding encoding;
101                 Decoder decoder;
102                 StringBuilder line_builder;
103                 Stream base_stream;
104
105                 //
106                 // Decoded bytes in decoded_buffer.
107                 //
108                 int decoded_count;
109
110                 //
111                 // Current position in the decoded_buffer
112                 //
113                 int pos;
114
115                 //
116                 // The buffer size that we are using
117                 //
118                 int buffer_size;
119
120                 int do_checks;
121                 
122                 bool mayBlock;
123
124 #if NET_4_5
125                 IDecoupledTask async_task;
126                 readonly bool leave_open;
127 #endif
128
129                 public new static readonly StreamReader Null =  new NullStreamReader ();
130                 
131                 private StreamReader() {}
132
133                 public StreamReader(Stream stream)
134                         : this (stream, Encoding.UTF8Unmarked, true, DefaultBufferSize) { }
135
136                 public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
137                         : this (stream, Encoding.UTF8Unmarked, detectEncodingFromByteOrderMarks, DefaultBufferSize) { }
138
139                 public StreamReader(Stream stream, Encoding encoding)
140                         : this (stream, encoding, true, DefaultBufferSize) { }
141
142                 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks)
143                         : this (stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize) { }
144
145 #if NET_4_5
146                 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
147                         : this (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false)
148                 {
149                 }
150
151                 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
152 #else
153                 const bool leave_open = false;
154
155                 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
156 #endif
157                 {
158 #if NET_4_5
159                         leave_open = leaveOpen;
160 #endif
161                         Initialize (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize);
162                 }
163
164                 public StreamReader(string path)
165                         : this (path, Encoding.UTF8Unmarked, true, DefaultFileBufferSize) { }
166
167                 public StreamReader(string path, bool detectEncodingFromByteOrderMarks)
168                         : this (path, Encoding.UTF8Unmarked, detectEncodingFromByteOrderMarks, DefaultFileBufferSize) { }
169
170                 public StreamReader(string path, Encoding encoding)
171                         : this (path, encoding, true, DefaultFileBufferSize) { }
172
173                 public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
174                         : this (path, encoding, detectEncodingFromByteOrderMarks, DefaultFileBufferSize) { }
175                 
176                 public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
177                 {
178                         if (null == path)
179                                 throw new ArgumentNullException("path");
180                         if (String.Empty == path)
181                                 throw new ArgumentException("Empty path not allowed");
182                         if (path.IndexOfAny (Path.InvalidPathChars) != -1)
183                                 throw new ArgumentException("path contains invalid characters");
184                         if (null == encoding)
185                                 throw new ArgumentNullException ("encoding");
186                         if (bufferSize <= 0)
187                                 throw new ArgumentOutOfRangeException ("bufferSize", "The minimum size of the buffer must be positive");
188
189                         Stream stream = (Stream) File.OpenRead (path);
190                         Initialize (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize);
191                 }
192
193                 internal void Initialize (Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
194                 {
195                         if (null == stream)
196                                 throw new ArgumentNullException ("stream");
197                         if (null == encoding)
198                                 throw new ArgumentNullException ("encoding");
199                         if (!stream.CanRead)
200                                 throw new ArgumentException ("Cannot read stream");
201                         if (bufferSize <= 0)
202                                 throw new ArgumentOutOfRangeException ("bufferSize", "The minimum size of the buffer must be positive");
203
204                         if (bufferSize < MinimumBufferSize)
205                                 bufferSize = MinimumBufferSize;
206                         
207                         // since GetChars() might add flushed character, it 
208                         // should have additional char buffer for extra 1 
209                         // (probably 1 is ok, but might be insufficient. I'm not sure)
210                         var decoded_buffer_size = encoding.GetMaxCharCount (bufferSize) + 1;
211
212                         //
213                         // Instead of allocating a new default buffer use the
214                         // last one if there is any available
215                         //
216                         if (bufferSize <= DefaultBufferSize && input_buffer_recycle != null) {
217                                 lock (input_buffer_recycle_lock) {
218                                         if (input_buffer_recycle != null) {
219                                                 input_buffer = input_buffer_recycle;
220                                                 input_buffer_recycle = null;
221                                         }
222                                         
223                                         if (decoded_buffer_recycle != null && decoded_buffer_size <= decoded_buffer_recycle.Length) {
224                                                 decoded_buffer = decoded_buffer_recycle;
225                                                 decoded_buffer_recycle = null;
226                                         }
227                                 }
228                         }
229                         
230                         if (input_buffer == null)
231                                 input_buffer = new byte [bufferSize];
232                         else
233                                 Array.Clear (input_buffer, 0, bufferSize);
234                         
235                         if (decoded_buffer == null)
236                                 decoded_buffer = new char [decoded_buffer_size];
237                         else
238                                 Array.Clear (decoded_buffer, 0, decoded_buffer_size);
239
240                         base_stream = stream;           
241                         this.buffer_size = bufferSize;
242                         this.encoding = encoding;
243                         decoder = encoding.GetDecoder ();
244
245                         byte [] preamble = encoding.GetPreamble ();
246                         do_checks = detectEncodingFromByteOrderMarks ? 1 : 0;
247                         do_checks += (preamble.Length == 0) ? 0 : 2;
248                         
249                         decoded_count = 0;
250                         pos = 0;
251                 }
252
253                 public virtual Stream BaseStream {
254                         get {
255                                 return base_stream;
256                         }
257                 }
258
259                 public virtual Encoding CurrentEncoding {
260                         get {
261                                 if (encoding == null)
262                                         throw new Exception ();
263                                 return encoding;
264                         }
265                 }
266
267                 public bool EndOfStream {
268                         get { return Peek () < 0; }
269                 }
270
271                 public override void Close ()
272                 {
273                         Dispose (true);
274                 }
275
276                 protected override void Dispose (bool disposing)
277                 {
278                         if (disposing && base_stream != null && !leave_open)
279                                 base_stream.Close ();
280                         
281                         if (input_buffer != null && input_buffer.Length == DefaultBufferSize && input_buffer_recycle == null) {
282                                 lock (input_buffer_recycle_lock) {
283                                         if (input_buffer_recycle == null) {
284                                                 input_buffer_recycle = input_buffer;
285                                         }
286                                         
287                                         if (decoded_buffer_recycle == null) {
288                                                 decoded_buffer_recycle = decoded_buffer;
289                                         }
290                                 }
291                         }
292                         
293                         input_buffer = null;
294                         decoded_buffer = null;
295                         encoding = null;
296                         decoder = null;
297                         base_stream = null;
298                         base.Dispose (disposing);
299                 }
300
301                 //
302                 // Provides auto-detection of the encoding, as well as skipping over
303                 // byte marks at the beginning of a stream.
304                 //
305                 int DoChecks (int count)
306                 {
307                         if ((do_checks & 2) == 2){
308                                 byte [] preamble = encoding.GetPreamble ();
309                                 int c = preamble.Length;
310                                 if (count >= c){
311                                         int i;
312                                         
313                                         for (i = 0; i < c; i++)
314                                                 if (input_buffer [i] != preamble [i])
315                                                         break;
316
317                                         if (i == c)
318                                                 return i;
319                                 }
320                         }
321
322                         if ((do_checks & 1) == 1){
323                                 if (count < 2)
324                                         return 0;
325
326                                 if (input_buffer [0] == 0xfe && input_buffer [1] == 0xff){
327                                         this.encoding = Encoding.BigEndianUnicode;
328                                         return 2;
329                                 }
330                                 if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe && count < 4) {
331                                         // If we don't have enough bytes we can't check for UTF32, so use Unicode
332                                         this.encoding = Encoding.Unicode;
333                                         return 2;
334                                 }
335
336                                 if (count < 3)
337                                         return 0;
338
339                                 if (input_buffer [0] == 0xef && input_buffer [1] == 0xbb && input_buffer [2] == 0xbf){
340                                         this.encoding = Encoding.UTF8Unmarked;
341                                         return 3;
342                                 }
343
344                                 if (count < 4) {
345                                         if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe && input_buffer [2] != 0) {
346                                                 this.encoding = Encoding.Unicode;
347                                                 return 2;
348                                         }
349                                         return 0;
350                                 }
351
352                                 if (input_buffer [0] == 0 && input_buffer [1] == 0
353                                         && input_buffer [2] == 0xfe && input_buffer [3] == 0xff)
354                                 {
355                                         this.encoding = Encoding.BigEndianUTF32;
356                                         return 4;
357                                 }
358
359                                 if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe) {
360                                         if (input_buffer [2] == 0 && input_buffer[3] == 0) {
361                                                 this.encoding = Encoding.UTF32;
362                                                 return 4;
363                                         }
364
365                                         this.encoding = Encoding.Unicode;
366                                         return 2;
367                                 }
368                         }
369
370                         return 0;
371                 }
372
373                 public void DiscardBufferedData ()
374                 {
375                         CheckState ();
376
377                         pos = decoded_count = 0;
378                         mayBlock = false;
379                         // Discard internal state of the decoder too.
380                         decoder = encoding.GetDecoder ();
381                 }
382                 
383                 // the buffer is empty, fill it again
384                 // Keep in sync with ReadBufferAsync
385                 int ReadBuffer ()
386                 {
387                         pos = 0;
388
389                         // keep looping until the decoder gives us some chars
390                         decoded_count = 0;
391                         do {
392                                 var cbEncoded = base_stream.Read (input_buffer, 0, buffer_size);
393                                 if (cbEncoded <= 0)
394                                         return 0;
395
396                                 decoded_count = ReadBufferCore (cbEncoded);
397                         } while (decoded_count == 0);
398
399                         return decoded_count;
400                 }
401
402                 int ReadBufferCore (int cbEncoded)
403                 {
404                         int parse_start;
405
406                         mayBlock = cbEncoded < buffer_size;
407                         if (do_checks > 0){
408                                 Encoding old = encoding;
409                                 parse_start = DoChecks (cbEncoded);
410                                 if (old != encoding){
411                                         int old_decoded_size = old.GetMaxCharCount (buffer_size) + 1;
412                                         int new_decoded_size = encoding.GetMaxCharCount (buffer_size) + 1;
413                                         if (old_decoded_size != new_decoded_size)
414                                                 decoded_buffer = new char [new_decoded_size];
415                                         decoder = encoding.GetDecoder ();
416                                 }
417                                 do_checks = 0;
418                                 cbEncoded -= parse_start;
419                         } else {
420                                 parse_start = 0;
421                         }
422                                 
423                         return decoder.GetChars (input_buffer, parse_start, cbEncoded, decoded_buffer, 0);
424                 }
425
426                 //
427                 // Peek can block:
428                 // http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=96484
429                 //
430                 public override int Peek ()
431                 {
432                         CheckState ();
433
434                         if (pos >= decoded_count && ReadBuffer () == 0)
435                                 return -1;
436
437                         return decoded_buffer [pos];
438                 }
439
440                 //
441                 // Used internally by our console, as it previously depended on Peek() being a
442                 // routine that would not block.
443                 //
444                 internal bool DataAvailable ()
445                 {
446                         return pos < decoded_count;
447                 }
448                 
449                 public override int Read ()
450                 {
451                         CheckState ();
452
453                         if (pos >= decoded_count && ReadBuffer () == 0)
454                                 return -1;
455
456                         return decoded_buffer [pos++];
457                 }
458
459                 // Keep in sync with ReadAsync
460                 public override int Read ([In, Out] char[] buffer, int index, int count)
461                 {
462                         if (buffer == null)
463                                 throw new ArgumentNullException ("buffer");
464                         if (index < 0)
465                                 throw new ArgumentOutOfRangeException ("index", "< 0");
466                         if (count < 0)
467                                 throw new ArgumentOutOfRangeException ("count", "< 0");
468                         // re-ordered to avoid possible integer overflow
469                         if (index > buffer.Length - count)
470                                 throw new ArgumentException ("index + count > buffer.Length");
471
472                         CheckState ();
473
474                         int chars_read = 0;
475                         while (count > 0) {
476                                 if (pos >= decoded_count && ReadBuffer () == 0)
477                                         return chars_read > 0 ? chars_read : 0;
478
479                                 int cch = Math.Min (decoded_count - pos, count);
480                                 Array.Copy (decoded_buffer, pos, buffer, index, cch);
481                                 pos += cch;
482                                 index += cch;
483                                 count -= cch;
484                                 chars_read += cch;
485                                 if (mayBlock)
486                                         break;
487                         }
488                         return chars_read;
489                 }
490
491                 bool foundCR;
492                 int FindNextEOL ()
493                 {
494                         char c = '\0';
495                         for (; pos < decoded_count; pos++) {
496                                 c = decoded_buffer [pos];
497                                 if (c == '\n') {
498                                         pos++;
499                                         int res = (foundCR) ? (pos - 2) : (pos - 1);
500                                         if (res < 0)
501                                                 res = 0; // if a new buffer starts with a \n and there was a \r at
502                                                         // the end of the previous one, we get here.
503                                         foundCR = false;
504                                         return res;
505                                 } else if (foundCR) {
506                                         foundCR = false;
507                                         if (pos == 0)
508                                                 return -2; // Need to flush the current buffered line.
509                                                            // This is a \r at the end of the previous decoded buffer that
510                                                            // is not followed by a \n in the current decoded buffer.
511                                         return pos - 1;
512                                 }
513
514                                 foundCR = (c == '\r');
515                         }
516
517                         return -1;
518                 }
519
520                 // Keep in sync with ReadLineAsync
521                 public override string ReadLine()
522                 {
523                         CheckState ();
524
525                         if (pos >= decoded_count && ReadBuffer () == 0)
526                                 return null;
527
528                         int begin = pos;
529                         int end = FindNextEOL ();
530                         if (end < decoded_count && end >= begin)
531                                 return new string (decoded_buffer, begin, end - begin);
532                         if (end == -2)
533                                 return line_builder.ToString (0, line_builder.Length);
534
535                         if (line_builder == null)
536                                 line_builder = new StringBuilder ();
537                         else
538                                 line_builder.Length = 0;
539
540                         while (true) {
541                                 if (foundCR) // don't include the trailing CR if present
542                                         decoded_count--;
543
544                                 line_builder.Append (decoded_buffer, begin, decoded_count - begin);
545                                 if (ReadBuffer () == 0) {
546                                         if (line_builder.Capacity > 32768) {
547                                                 StringBuilder sb = line_builder;
548                                                 line_builder = null;
549                                                 return sb.ToString (0, sb.Length);
550                                         }
551                                         return line_builder.ToString (0, line_builder.Length);
552                                 }
553
554                                 begin = pos;
555                                 end = FindNextEOL ();
556                                 if (end < decoded_count && end >= begin) {
557                                         line_builder.Append (decoded_buffer, begin, end - begin);
558                                         if (line_builder.Capacity > 32768) {
559                                                 StringBuilder sb = line_builder;
560                                                 line_builder = null;
561                                                 return sb.ToString (0, sb.Length);
562                                         }
563                                         return line_builder.ToString (0, line_builder.Length);
564                                 }
565
566                                 if (end == -2)
567                                         return line_builder.ToString (0, line_builder.Length);
568                         }
569                 }
570
571                 // Keep in sync with ReadToEndAsync
572                 public override string ReadToEnd()
573                 {
574                         CheckState ();
575
576                         StringBuilder text = new StringBuilder ();
577
578                         do {
579                                 text.Append (decoded_buffer, pos, decoded_count - pos);
580                         } while (ReadBuffer () != 0);
581
582                         return text.ToString ();
583                 }
584
585                 void CheckState ()
586                 {
587                         if (base_stream == null)
588                                 throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader");
589
590 #if NET_4_5
591                         if (async_task != null && !async_task.IsCompleted)
592                                 throw new InvalidOperationException ();
593 #endif
594                 }
595
596 #if NET_4_5
597                 public override int ReadBlock ([In, Out] char[] buffer, int index, int count)
598                 {
599                         if (buffer == null)
600                                 throw new ArgumentNullException ("buffer");
601                         if (index < 0)
602                                 throw new ArgumentOutOfRangeException ("index", "< 0");
603                         if (count < 0)
604                                 throw new ArgumentOutOfRangeException ("count", "< 0");
605                         // re-ordered to avoid possible integer overflow
606                         if (index > buffer.Length - count)
607                                 throw new ArgumentException ("index + count > buffer.Length");
608
609                         CheckState ();
610
611                         return base.ReadBlock (buffer, index, count);
612                 }
613
614                 public override Task<int> ReadAsync (char[] buffer, int index, int count)
615                 {
616                         if (buffer == null)
617                                 throw new ArgumentNullException ("buffer");
618                         if (index < 0)
619                                 throw new ArgumentOutOfRangeException ("index", "< 0");
620                         if (count < 0)
621                                 throw new ArgumentOutOfRangeException ("count", "< 0");
622                         // re-ordered to avoid possible integer overflow
623                         if (index > buffer.Length - count)
624                                 throw new ArgumentException ("index + count > buffer.Length");
625
626                         CheckState ();
627
628                         DecoupledTask<int> res;
629                         async_task = res = new DecoupledTask<int> (ReadAsyncCore (buffer, index, count));
630                         return res.Task;
631                 }
632
633                 async Task<int> ReadAsyncCore (char[] buffer, int index, int count)
634                 {
635                         int chars_read = 0;
636
637                         while (count > 0) {
638                                 if (pos >= decoded_count && await ReadBufferAsync ().ConfigureAwait (false) == 0)
639                                         return chars_read > 0 ? chars_read : 0;
640
641                                 int cch = Math.Min (decoded_count - pos, count);
642                                 Array.Copy (decoded_buffer, pos, buffer, index, cch);
643                                 pos += cch;
644                                 index += cch;
645                                 count -= cch;
646                                 chars_read += cch;
647                                 if (mayBlock)
648                                         break;
649                         }
650
651                         return chars_read;
652                 }
653
654                 public override Task<int> ReadBlockAsync (char[] buffer, int index, int count)
655                 {
656                         if (buffer == null)
657                                 throw new ArgumentNullException ("buffer");
658                         if (index < 0)
659                                 throw new ArgumentOutOfRangeException ("index", "< 0");
660                         if (count < 0)
661                                 throw new ArgumentOutOfRangeException ("count", "< 0");
662                         // re-ordered to avoid possible integer overflow
663                         if (index > buffer.Length - count)
664                                 throw new ArgumentException ("index + count > buffer.Length");
665
666                         CheckState ();
667
668                         DecoupledTask<int> res;
669                         async_task = res = new DecoupledTask<int> (ReadAsyncCore (buffer, index, count));
670                         return res.Task;
671                 }
672
673                 public override Task<string> ReadLineAsync ()
674                 {
675                         CheckState ();
676
677                         DecoupledTask<string> res;
678                         async_task = res = new DecoupledTask<string> (ReadLineAsyncCore ());
679                         return res.Task;
680                 }
681
682                 async Task<string> ReadLineAsyncCore ()
683                 {
684                         if (pos >= decoded_count && await ReadBufferAsync ().ConfigureAwait (false) == 0)
685                                 return null;
686
687                         int begin = pos;
688                         int end = FindNextEOL ();
689                         if (end < decoded_count && end >= begin)
690                                 return new string (decoded_buffer, begin, end - begin);
691                         if (end == -2)
692                                 return line_builder.ToString (0, line_builder.Length);
693
694                         if (line_builder == null)
695                                 line_builder = new StringBuilder ();
696                         else
697                                 line_builder.Length = 0;
698
699                         while (true) {
700                                 if (foundCR) // don't include the trailing CR if present
701                                         decoded_count--;
702
703                                 line_builder.Append (decoded_buffer, begin, decoded_count - begin);
704                                 if (await ReadBufferAsync ().ConfigureAwait (false) == 0) {
705                                         if (line_builder.Capacity > 32768) {
706                                                 StringBuilder sb = line_builder;
707                                                 line_builder = null;
708                                                 return sb.ToString (0, sb.Length);
709                                         }
710                                         return line_builder.ToString (0, line_builder.Length);
711                                 }
712
713                                 begin = pos;
714                                 end = FindNextEOL ();
715                                 if (end < decoded_count && end >= begin) {
716                                         line_builder.Append (decoded_buffer, begin, end - begin);
717                                         if (line_builder.Capacity > 32768) {
718                                                 StringBuilder sb = line_builder;
719                                                 line_builder = null;
720                                                 return sb.ToString (0, sb.Length);
721                                         }
722                                         return line_builder.ToString (0, line_builder.Length);
723                                 }
724
725                                 if (end == -2)
726                                         return line_builder.ToString (0, line_builder.Length);
727                         }
728                 }
729
730                 public override Task<string> ReadToEndAsync ()
731                 {
732                         CheckState ();
733
734                         DecoupledTask<string> res;
735                         async_task = res = new DecoupledTask<string> (ReadToEndAsyncCore ());
736                         return res.Task;
737                 }
738
739                 async Task<string> ReadToEndAsyncCore ()
740                 {
741                         StringBuilder text = new StringBuilder ();
742
743                         do {
744                                 text.Append (decoded_buffer, pos, decoded_count - pos);
745                         } while (await ReadBufferAsync () != 0);
746
747                         return text.ToString ();
748                 }
749
750                 async Task<int> ReadBufferAsync ()
751                 {
752                         pos = 0;
753
754                         // keep looping until the decoder gives us some chars
755                         decoded_count = 0;
756                         do {
757                                 var cbEncoded = await base_stream.ReadAsync (input_buffer, 0, buffer_size).ConfigureAwait (false);
758                                 if (cbEncoded <= 0)
759                                         return 0;
760
761                                 decoded_count = ReadBufferCore (cbEncoded);
762                         } while (decoded_count == 0);
763
764                         return decoded_count;
765                 }
766 #endif
767         }
768 }