[System]: WebConnection: improve chunked reads and async callbacks.
[mono.git] / mcs / class / System / System.Net / WebConnectionStream.cs
1 //
2 // System.Net.WebConnectionStream
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
8 // (C) 2004 Novell, Inc (http://www.novell.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.IO;
33 using System.Text;
34 using System.Threading;
35
36 namespace System.Net
37 {
38         class WebConnectionStream : Stream
39         {
40                 static byte [] crlf = new byte [] { 13, 10 };
41                 bool isRead;
42                 WebConnection cnc;
43                 HttpWebRequest request;
44                 byte [] readBuffer;
45                 int readBufferOffset;
46                 int readBufferSize;
47                 int stream_length; // -1 when CL not present
48                 long contentLength;
49                 long totalRead;
50                 internal long totalWritten;
51                 bool nextReadCalled;
52                 int pendingReads;
53                 int pendingWrites;
54                 ManualResetEvent pending;
55                 bool allowBuffering;
56                 bool sendChunked;
57                 MemoryStream writeBuffer;
58                 bool requestWritten;
59                 byte [] headers;
60                 bool disposed;
61                 bool headersSent;
62                 object locker = new object ();
63                 bool initRead;
64                 bool read_eof;
65                 bool complete_request_written;
66                 int read_timeout;
67                 int write_timeout;
68                 AsyncCallback cb_wrapper; // Calls to ReadCallbackWrapper or WriteCallbacWrapper
69                 internal bool IgnoreIOErrors;
70
71                 public WebConnectionStream (WebConnection cnc, WebConnectionData data)
72                 {          
73                         if (data == null)
74                                 throw new InvalidOperationException ("data was not initialized");
75                         if (data.Headers == null)
76                                 throw new InvalidOperationException ("data.Headers was not initialized");
77                         if (data.request == null)
78                                 throw new InvalidOperationException ("data.request was not initialized");
79                         isRead = true;
80                         cb_wrapper = new AsyncCallback (ReadCallbackWrapper);
81                         pending = new ManualResetEvent (true);
82                         this.request = data.request;
83                         read_timeout = request.ReadWriteTimeout;
84                         write_timeout = read_timeout;
85                         this.cnc = cnc;
86                         string contentType = data.Headers ["Transfer-Encoding"];
87                         bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
88                         string clength = data.Headers ["Content-Length"];
89                         if (!chunkedRead && clength != null && clength != "") {
90                                 try {
91                                         contentLength = Int32.Parse (clength);
92                                         if (contentLength == 0 && !IsNtlmAuth ()) {
93                                                 ReadAll ();
94                                         }
95                                 } catch {
96                                         contentLength = Int64.MaxValue;
97                                 }
98                         } else {
99                                 contentLength = Int64.MaxValue;
100                         }
101
102                         // Negative numbers?
103                         if (!Int32.TryParse (clength, out stream_length))
104                                 stream_length = -1;
105                 }
106
107                 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
108                 {
109                         read_timeout = request.ReadWriteTimeout;
110                         write_timeout = read_timeout;
111                         isRead = false;
112                         cb_wrapper = new AsyncCallback (WriteCallbackWrapper);
113                         this.cnc = cnc;
114                         this.request = request;
115                         allowBuffering = request.InternalAllowBuffering;
116                         sendChunked = request.SendChunked;
117                         if (sendChunked)
118                                 pending = new ManualResetEvent (true);
119                         else if (allowBuffering)
120                                 writeBuffer = new MemoryStream ();
121                 }
122
123                 bool CheckAuthHeader (string headerName)
124                 {
125                         var authHeader = cnc.Data.Headers [headerName];
126                         return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
127                 }
128
129                 bool IsNtlmAuth ()
130                 {
131                         bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address));
132                         if (isProxy && CheckAuthHeader ("Proxy-Authenticate"))
133                                 return true;
134                         return CheckAuthHeader ("WWW-Authenticate");
135                 }
136
137                 internal void CheckResponseInBuffer ()
138                 {
139                         if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) {
140                                 if (!IsNtlmAuth ())
141                                         ReadAll ();
142                         }
143                 }
144
145                 internal HttpWebRequest Request {
146                         get { return request; }
147                 }
148
149                 internal WebConnection Connection {
150                         get { return cnc; }
151                 }
152                 public override bool CanTimeout {
153                         get { return true; }
154                 }
155
156                 public override int ReadTimeout {
157                         get {
158                                 return read_timeout;
159                         }
160
161                         set {
162                                 if (value < -1)
163                                         throw new ArgumentOutOfRangeException ("value");
164                                 read_timeout = value;
165                         }
166                 }
167
168                 public override int WriteTimeout {
169                         get {
170                                 return write_timeout;
171                         }
172
173                         set {
174                                 if (value < -1)
175                                         throw new ArgumentOutOfRangeException ("value");
176                                 write_timeout = value;
177                         }
178                 }
179
180                 internal bool CompleteRequestWritten {
181                         get { return complete_request_written; }
182                 }
183
184                 internal bool SendChunked {
185                         set { sendChunked = value; }
186                 }
187
188                 internal byte [] ReadBuffer {
189                         set { readBuffer = value; }
190                 }
191
192                 internal int ReadBufferOffset {
193                         set { readBufferOffset = value; }
194                 }
195                 
196                 internal int ReadBufferSize {
197                         set { readBufferSize = value; }
198                 }
199                 
200                 internal byte[] WriteBuffer {
201                         get { return writeBuffer.GetBuffer (); }
202                 }
203
204                 internal int WriteBufferLength {
205                         get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); }
206                 }
207
208                 internal void ForceCompletion ()
209                 {
210                         if (!nextReadCalled) {
211                                 if (contentLength == Int64.MaxValue)
212                                         contentLength = 0;
213                                 nextReadCalled = true;
214                                 cnc.NextRead ();
215                         }
216                 }
217
218                 internal void CheckComplete ()
219                 {
220                         bool nrc = nextReadCalled;
221                         if (!nrc && readBufferSize - readBufferOffset == contentLength) {
222                                 nextReadCalled = true;
223                                 cnc.NextRead ();
224                         }
225                 }
226
227                 internal void ReadAll ()
228                 {
229                         if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
230                                 if (isRead && !nextReadCalled) {
231                                         nextReadCalled = true;
232                                         cnc.NextRead ();
233                                 }
234                                 return;
235                         }
236
237                         if (!pending.WaitOne (ReadTimeout))
238                                 throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
239                         lock (locker) {
240                                 if (totalRead >= contentLength)
241                                         return;
242                                 
243                                 byte [] b = null;
244                                 int diff = readBufferSize - readBufferOffset;
245                                 int new_size;
246
247                                 if (contentLength == Int64.MaxValue) {
248                                         MemoryStream ms = new MemoryStream ();
249                                         byte [] buffer = null;
250                                         if (readBuffer != null && diff > 0) {
251                                                 ms.Write (readBuffer, readBufferOffset, diff);
252                                                 if (readBufferSize >= 8192)
253                                                         buffer = readBuffer;
254                                         }
255
256                                         if (buffer == null)
257                                                 buffer = new byte [8192];
258
259                                         int read;
260                                         while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0)
261                                                 ms.Write (buffer, 0, read);
262
263                                         b = ms.GetBuffer ();
264                                         new_size = (int) ms.Length;
265                                         contentLength = new_size;
266                                 } else {
267                                         new_size = (int) (contentLength - totalRead);
268                                         b = new byte [new_size];
269                                         if (readBuffer != null && diff > 0) {
270                                                 if (diff > new_size)
271                                                         diff = new_size;
272
273                                                 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
274                                         }
275                                         
276                                         int remaining = new_size - diff;
277                                         int r = -1;
278                                         while (remaining > 0 && r != 0) {
279                                                 r = cnc.Read (request, b, diff, remaining);
280                                                 remaining -= r;
281                                                 diff += r;
282                                         }
283                                 }
284
285                                 readBuffer = b;
286                                 readBufferOffset = 0;
287                                 readBufferSize = new_size;
288                                 totalRead = 0;
289                                 nextReadCalled = true;
290                         }
291
292                         cnc.NextRead ();
293                 }
294
295                 void WriteCallbackWrapper (IAsyncResult r)
296                 {
297                         WebAsyncResult result = r as WebAsyncResult;
298                         if (result != null && result.AsyncWriteAll)
299                                 return;
300
301                         if (r.AsyncState != null) {
302                                 result = (WebAsyncResult) r.AsyncState;
303                                 result.InnerAsyncResult = r;
304                                 result.DoCallback ();
305                         } else {
306                                 try {
307                                         EndWrite (r);
308                                 } catch {
309                                 }
310                         }
311                 }
312
313                 void ReadCallbackWrapper (IAsyncResult r)
314                 {
315                         WebAsyncResult result;
316                         if (r.AsyncState != null) {
317                                 result = (WebAsyncResult) r.AsyncState;
318                                 result.InnerAsyncResult = r;
319                                 result.DoCallback ();
320                         } else {
321                                 try {
322                                         EndRead (r);
323                                 } catch {
324                                 }
325                         }
326                 }
327
328                 public override int Read (byte [] buffer, int offset, int size)
329                 {
330                         AsyncCallback cb = cb_wrapper;
331                         WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
332                         if (!res.IsCompleted && !res.WaitUntilComplete (ReadTimeout, false)) {
333                                 nextReadCalled = true;
334                                 cnc.Close (true);
335                                 throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
336                         }
337
338                         return EndRead (res);
339                 }
340
341                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
342                                                         AsyncCallback cb, object state)
343                 {
344                         if (!isRead)
345                                 throw new NotSupportedException ("this stream does not allow reading");
346
347                         if (buffer == null)
348                                 throw new ArgumentNullException ("buffer");
349
350                         int length = buffer.Length;
351                         if (offset < 0 || length < offset)
352                                 throw new ArgumentOutOfRangeException ("offset");
353                         if (size < 0 || (length - offset) < size)
354                                 throw new ArgumentOutOfRangeException ("size");
355
356                         lock (locker) {
357                                 pendingReads++;
358                                 pending.Reset ();
359                         }
360
361                         WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
362                         result.AsyncObject = request;
363                         if (totalRead >= contentLength) {
364                                 result.SetCompleted (true, -1);
365                                 result.DoCallback ();
366                                 return result;
367                         }
368                         
369                         int remaining = readBufferSize - readBufferOffset;
370                         if (remaining > 0) {
371                                 int copy = (remaining > size) ? size : remaining;
372                                 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
373                                 readBufferOffset += copy;
374                                 offset += copy;
375                                 size -= copy;
376                                 totalRead += copy;
377                                 if (size == 0 || totalRead >= contentLength) {
378                                         result.SetCompleted (true, copy);
379                                         result.DoCallback ();
380                                         return result;
381                                 }
382                                 result.NBytes = copy;
383                         }
384
385                         if (cb != null)
386                                 cb = cb_wrapper;
387
388                         if (contentLength != Int64.MaxValue && contentLength - totalRead < size)
389                                 size = (int)(contentLength - totalRead);
390
391                         if (!read_eof) {
392                                 cnc.ReadAsync (request, buffer, offset, size, result);
393                         } else {
394                                 result.SetCompleted (true, result.NBytes);
395                                 result.DoCallback ();
396                         }
397                         return result;
398                 }
399
400                 public override int EndRead (IAsyncResult r)
401                 {
402                         WebAsyncResult result = (WebAsyncResult) r;
403                         int nb = result.NBytes;
404
405                         if (result.EndCalled)
406                                 return (nb >= 0) ? nb : 0;
407                         result.EndCalled = true;
408
409                         lock (locker) {
410                                 pendingReads--;
411                                 if (pendingReads == 0)
412                                         pending.Set ();
413                         }
414
415                         if (result.GotException) {
416                                 nextReadCalled = true;
417                                 cnc.Close (true);
418                                 throw result.Exception;
419                         }
420
421                         if (nb < 0) {
422                                 read_eof = true;
423                         } else {
424                                 totalRead += result.NBytes;
425                                 if (nb == 0)
426                                         contentLength = totalRead;
427                         }
428
429                         if (totalRead >= contentLength && !nextReadCalled)
430                                 ReadAll ();
431
432                         return (nb >= 0) ? nb : 0;
433                 }
434
435                 void WriteAsyncCB (IAsyncResult r)
436                 {
437                         WebAsyncResult result = (WebAsyncResult) r.AsyncState;
438                         result.InnerAsyncResult = null;
439
440                         try {
441                                 cnc.EndWrite (request, true, r);
442                                 result.SetCompleted (false, 0);
443                                 if (!initRead) {
444                                         initRead = true;
445                                         WebConnection.InitRead (cnc);
446                                 }
447                         } catch (Exception e) {
448                                 KillBuffer ();
449                                 nextReadCalled = true;
450                                 cnc.Close (true);
451                                 if (e is System.Net.Sockets.SocketException)
452                                         e = new IOException ("Error writing request", e);
453                                 result.SetCompleted (false, e);
454                         }
455
456                         if (allowBuffering && !sendChunked && request.ContentLength > 0 && totalWritten == request.ContentLength)
457                                 complete_request_written = true;
458
459                         result.DoCallback ();
460                 }
461
462                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
463                                                         AsyncCallback cb, object state)
464                 {
465                         if (request.Aborted)
466                                 throw new WebException ("The request was canceled.", null, WebExceptionStatus.RequestCanceled);
467
468                         if (isRead)
469                                 throw new NotSupportedException ("this stream does not allow writing");
470
471                         if (buffer == null)
472                                 throw new ArgumentNullException ("buffer");
473
474                         int length = buffer.Length;
475                         if (offset < 0 || length < offset)
476                                 throw new ArgumentOutOfRangeException ("offset");
477                         if (size < 0 || (length - offset) < size)
478                                 throw new ArgumentOutOfRangeException ("size");
479
480                         if (sendChunked) {
481                                 lock (locker) {
482                                         pendingWrites++;
483                                         pending.Reset ();
484                                 }
485                         }
486
487                         WebAsyncResult result = new WebAsyncResult (cb, state);
488                         AsyncCallback callback = new AsyncCallback (WriteAsyncCB);
489
490                         if (sendChunked) {
491                                 requestWritten = true;
492
493                                 string cSize = String.Format ("{0:X}\r\n", size);
494                                 byte[] head = Encoding.ASCII.GetBytes (cSize);
495                                 int chunkSize = 2 + size + head.Length;
496                                 byte[] newBuffer = new byte [chunkSize];
497                                 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
498                                 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
499                                 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
500
501                                 if (allowBuffering) {
502                                         if (writeBuffer == null)
503                                                 writeBuffer = new MemoryStream ();
504                                         writeBuffer.Write (buffer, offset, size);
505                                         totalWritten += size;
506                                 }
507
508                                 buffer = newBuffer;
509                                 offset = 0;
510                                 size = chunkSize;
511                         } else {
512                                 CheckWriteOverflow (request.ContentLength, totalWritten, size);
513
514                                 if (allowBuffering) {
515                                         if (writeBuffer == null)
516                                                 writeBuffer = new MemoryStream ();
517                                         writeBuffer.Write (buffer, offset, size);
518                                         totalWritten += size;
519
520                                         if (request.ContentLength <= 0 || totalWritten < request.ContentLength) {
521                                                 result.SetCompleted (true, 0);
522                                                 result.DoCallback ();
523                                                 return result;
524                                         }
525
526                                         result.AsyncWriteAll = true;
527                                         requestWritten = true;
528                                         buffer = writeBuffer.GetBuffer ();
529                                         offset = 0;
530                                         size = (int)totalWritten;
531                                 }
532                         }
533
534                         try {
535                                 result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result);
536                                 if (result.InnerAsyncResult == null) {
537                                         if (!result.IsCompleted)
538                                                 result.SetCompleted (true, 0);
539                                         result.DoCallback ();
540                                 }
541                         } catch (Exception) {
542                                 if (!IgnoreIOErrors)
543                                         throw;
544                                 result.SetCompleted (true, 0);
545                                 result.DoCallback ();
546                         }
547                         totalWritten += size;
548                         return result;
549                 }
550
551                 void CheckWriteOverflow (long contentLength, long totalWritten, long size)
552                 {
553                         if (contentLength == -1)
554                                 return;
555
556                         long avail = contentLength - totalWritten;
557                         if (size > avail) {
558                                 KillBuffer ();
559                                 nextReadCalled = true;
560                                 cnc.Close (true);
561                                 throw new ProtocolViolationException (
562                                         "The number of bytes to be written is greater than " +
563                                         "the specified ContentLength.");
564                         }
565                 }
566
567                 public override void EndWrite (IAsyncResult r)
568                 {
569                         if (r == null)
570                                 throw new ArgumentNullException ("r");
571
572                         WebAsyncResult result = r as WebAsyncResult;
573                         if (result == null)
574                                 throw new ArgumentException ("Invalid IAsyncResult");
575
576                         if (result.EndCalled)
577                                 return;
578
579                         if (sendChunked) {
580                                 lock (locker) {
581                                         pendingWrites--;
582                                         if (pendingWrites <= 0)
583                                                 pending.Set ();
584                                 }
585                         }
586
587                         result.EndCalled = true;
588                         if (result.AsyncWriteAll) {
589                                 result.WaitUntilComplete ();
590                                 if (result.GotException)
591                                         throw result.Exception;
592                                 return;
593                         }
594
595                         if (allowBuffering && !sendChunked)
596                                 return;
597
598                         if (result.GotException)
599                                 throw result.Exception;
600                 }
601                 
602                 public override void Write (byte [] buffer, int offset, int size)
603                 {
604                         AsyncCallback cb = cb_wrapper;
605                         WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
606                         if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) {
607                                 KillBuffer ();
608                                 nextReadCalled = true;
609                                 cnc.Close (true);
610                                 throw new IOException ("Write timed out.");
611                         }
612
613                         EndWrite (res);
614                 }
615
616                 public override void Flush ()
617                 {
618                 }
619
620                 internal void SetHeadersAsync (bool setInternalLength, SimpleAsyncCallback callback)
621                 {
622                         SimpleAsyncResult.Run (r => SetHeadersAsync (r, setInternalLength), callback);
623                 }
624
625                 bool SetHeadersAsync (SimpleAsyncResult result, bool setInternalLength)
626                 {
627                         if (headersSent)
628                                 return false;
629
630                         string method = request.Method;
631                         bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
632                                               method == "TRACE");
633                         bool webdav = (method == "PROPFIND" || method == "PROPPATCH" || method == "MKCOL" ||
634                                       method == "COPY" || method == "MOVE" || method == "LOCK" ||
635                                       method == "UNLOCK");
636
637                         if (setInternalLength && !no_writestream && writeBuffer != null)
638                                 request.InternalContentLength = writeBuffer.Length;
639
640                         bool has_content = !no_writestream && (writeBuffer == null || request.ContentLength > -1);
641                         if (!(sendChunked || has_content || no_writestream || webdav))
642                                 return false;
643
644                         headersSent = true;
645                         headers = request.GetRequestHeaders ();
646
647                         var innerResult = cnc.BeginWrite (request, headers, 0, headers.Length, r => {
648                                 try {
649                                         cnc.EndWrite (request, true, r);
650                                         if (!initRead) {
651                                                 initRead = true;
652                                                 WebConnection.InitRead (cnc);
653                                         }
654                                         var cl = request.ContentLength;
655                                         if (!sendChunked && cl == 0)
656                                                 requestWritten = true;
657                                         result.SetCompleted (false);
658                                 } catch (WebException e) {
659                                         result.SetCompleted (false, e);
660                                 } catch (Exception e) {
661                                         result.SetCompleted (false, new WebException ("Error writing headers", e, WebExceptionStatus.SendFailure));
662                                 }
663                         }, null);
664
665                         return innerResult != null;
666                 }
667
668                 internal bool RequestWritten {
669                         get { return requestWritten; }
670                 }
671
672                 internal SimpleAsyncResult WriteRequestAsync (SimpleAsyncCallback callback)
673                 {
674                         var result = WriteRequestAsync (callback);
675                         try {
676                                 if (!WriteRequestAsync (result))
677                                         result.SetCompleted (true);
678                         } catch (Exception ex) {
679                                 result.SetCompleted (true, ex);
680                         }
681                         return result;
682                 }
683
684                 internal bool WriteRequestAsync (SimpleAsyncResult result)
685                 {
686                         if (requestWritten)
687                                 return false;
688
689                         requestWritten = true;
690                         if (sendChunked || !allowBuffering || writeBuffer == null)
691                                 return false;
692
693                         // Keep the call for a potential side-effect of GetBuffer
694                         var bytes = writeBuffer.GetBuffer ();
695                         var length = (int)writeBuffer.Length;
696                         if (request.ContentLength != -1 && request.ContentLength < length) {
697                                 nextReadCalled = true;
698                                 cnc.Close (true);
699                                 throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
700                                         WebExceptionStatus.ServerProtocolViolation, null);
701                         }
702
703                         SetHeadersAsync (true, inner => {
704                                 if (inner.GotException) {
705                                         result.SetCompleted (inner.CompletedSynchronously, inner.Exception);
706                                         return;
707                                 }
708
709                                 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100) {
710                                         result.SetCompleted (inner.CompletedSynchronously);
711                                         return;
712                                 }
713
714                                 if (!initRead) {
715                                         initRead = true;
716                                         WebConnection.InitRead (cnc);
717                                 }
718
719                                 if (length == 0) {
720                                         complete_request_written = true;
721                                         result.SetCompleted (inner.CompletedSynchronously);
722                                         return;
723                                 }
724
725                                 cnc.BeginWrite (request, bytes, 0, length, r => {
726                                         try {
727                                                 complete_request_written = cnc.EndWrite (request, false, r);
728                                                 result.SetCompleted (false);
729                                         } catch (Exception exc) {
730                                                 result.SetCompleted (false, exc);
731                                         }
732                                 }, null);
733                         });
734
735                         return true;
736                 }
737
738                 internal void InternalClose ()
739                 {
740                         disposed = true;
741                 }
742
743                 internal bool GetResponseOnClose {
744                         get; set;
745                 }
746
747                 public override void Close ()
748                 {
749                         if (GetResponseOnClose) {
750                                 if (disposed)
751                                         return;
752                                 disposed = true;
753                                 var response = (HttpWebResponse)request.GetResponse ();
754                                 response.ReadAll ();
755                                 response.Close ();
756                                 return;
757                         }
758
759                         if (sendChunked) {
760                                 if (disposed)
761                                         return;
762                                 disposed = true;
763                                 if (!pending.WaitOne (WriteTimeout)) {
764                                         throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
765                                 }
766                                 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
767                                 string err_msg = null;
768                                 cnc.Write (request, chunk, 0, chunk.Length, ref err_msg);
769                                 return;
770                         }
771
772                         if (isRead) {
773                                 if (!nextReadCalled) {
774                                         CheckComplete ();
775                                         // If we have not read all the contents
776                                         if (!nextReadCalled) {
777                                                 nextReadCalled = true;
778                                                 cnc.Close (true);
779                                         }
780                                 }
781                                 return;
782                         } else if (!allowBuffering) {
783                                 complete_request_written = true;
784                                 if (!initRead) {
785                                         initRead = true;
786                                         WebConnection.InitRead (cnc);
787                                 }
788                                 return;
789                         }
790
791                         if (disposed || requestWritten)
792                                 return;
793
794                         long length = request.ContentLength;
795
796                         if (!sendChunked && length != -1 && totalWritten != length) {
797                                 IOException io = new IOException ("Cannot close the stream until all bytes are written");
798                                 nextReadCalled = true;
799                                 cnc.Close (true);
800                                 throw new WebException ("Request was cancelled.", io, WebExceptionStatus.RequestCanceled);
801                         }
802
803                         // Commented out the next line to fix xamarin bug #1512
804                         //WriteRequest ();
805                         disposed = true;
806                 }
807
808                 internal void KillBuffer ()
809                 {
810                         writeBuffer = null;
811                 }
812
813                 public override long Seek (long a, SeekOrigin b)
814                 {
815                         throw new NotSupportedException ();
816                 }
817                 
818                 public override void SetLength (long a)
819                 {
820                         throw new NotSupportedException ();
821                 }
822                 
823                 public override bool CanSeek {
824                         get { return false; }
825                 }
826
827                 public override bool CanRead {
828                         get { return !disposed && isRead; }
829                 }
830
831                 public override bool CanWrite {
832                         get { return !disposed && !isRead; }
833                 }
834
835                 public override long Length {
836                         get {
837                                 if (!isRead)
838                                         throw new NotSupportedException ();
839                                 return stream_length;
840                         }
841                 }
842
843                 public override long Position {
844                         get { throw new NotSupportedException (); }
845                         set { throw new NotSupportedException (); }
846                 }
847         }
848 }
849