Convert blocking operations in HttpWebRequest and SslClientStream to non-blocking...
[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                 int contentLength;
49                 int 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 = Int32.MaxValue;
97                                 }
98                         } else {
99                                 contentLength = Int32.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 IsNtlmAuth ()
124                 {
125                         bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address));
126                         string header_name = (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate";
127                         string authHeader = cnc.Data.Headers [header_name];
128                         return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
129                 }
130
131                 internal void CheckResponseInBuffer ()
132                 {
133                         if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) {
134                                 if (!IsNtlmAuth ())
135                                         ReadAll ();
136                         }
137                 }
138
139                 internal HttpWebRequest Request {
140                         get { return request; }
141                 }
142
143                 internal WebConnection Connection {
144                         get { return cnc; }
145                 }
146                 public override bool CanTimeout {
147                         get { return true; }
148                 }
149
150                 public override int ReadTimeout {
151                         get {
152                                 return read_timeout;
153                         }
154
155                         set {
156                                 if (value < -1)
157                                         throw new ArgumentOutOfRangeException ("value");
158                                 read_timeout = value;
159                         }
160                 }
161
162                 public override int WriteTimeout {
163                         get {
164                                 return write_timeout;
165                         }
166
167                         set {
168                                 if (value < -1)
169                                         throw new ArgumentOutOfRangeException ("value");
170                                 write_timeout = value;
171                         }
172                 }
173
174                 internal bool CompleteRequestWritten {
175                         get { return complete_request_written; }
176                 }
177
178                 internal bool SendChunked {
179                         set { sendChunked = value; }
180                 }
181
182                 internal byte [] ReadBuffer {
183                         set { readBuffer = value; }
184                 }
185
186                 internal int ReadBufferOffset {
187                         set { readBufferOffset = value;}
188                 }
189                 
190                 internal int ReadBufferSize {
191                         set { readBufferSize = value; }
192                 }
193                 
194                 internal byte[] WriteBuffer {
195                         get { return writeBuffer.GetBuffer (); }
196                 }
197
198                 internal int WriteBufferLength {
199                         get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); }
200                 }
201
202                 internal void ForceCompletion ()
203                 {
204                         if (!nextReadCalled) {
205                                 if (contentLength == Int32.MaxValue)
206                                         contentLength = 0;
207                                 nextReadCalled = true;
208                                 cnc.NextRead ();
209                         }
210                 }
211                 
212                 internal void CheckComplete ()
213                 {
214                         bool nrc = nextReadCalled;
215                         if (!nrc && readBufferSize - readBufferOffset == contentLength) {
216                                 nextReadCalled = true;
217                                 cnc.NextRead ();
218                         }
219                 }
220
221                 internal void ReadAll ()
222                 {
223                         if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
224                                 if (isRead && !nextReadCalled) {
225                                         nextReadCalled = true;
226                                         cnc.NextRead ();
227                                 }
228                                 return;
229                         }
230
231                         pending.WaitOne ();
232                         lock (locker) {
233                                 if (totalRead >= contentLength)
234                                         return;
235                                 
236                                 byte [] b = null;
237                                 int diff = readBufferSize - readBufferOffset;
238                                 int new_size;
239
240                                 if (contentLength == Int32.MaxValue) {
241                                         MemoryStream ms = new MemoryStream ();
242                                         byte [] buffer = null;
243                                         if (readBuffer != null && diff > 0) {
244                                                 ms.Write (readBuffer, readBufferOffset, diff);
245                                                 if (readBufferSize >= 8192)
246                                                         buffer = readBuffer;
247                                         }
248
249                                         if (buffer == null)
250                                                 buffer = new byte [8192];
251
252                                         int read;
253                                         while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0)
254                                                 ms.Write (buffer, 0, read);
255
256                                         b = ms.GetBuffer ();
257                                         new_size = (int) ms.Length;
258                                         contentLength = new_size;
259                                 } else {
260                                         new_size = contentLength - totalRead;
261                                         b = new byte [new_size];
262                                         if (readBuffer != null && diff > 0) {
263                                                 if (diff > new_size)
264                                                         diff = new_size;
265
266                                                 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
267                                         }
268                                         
269                                         int remaining = new_size - diff;
270                                         int r = -1;
271                                         while (remaining > 0 && r != 0) {
272                                                 r = cnc.Read (request, b, diff, remaining);
273                                                 remaining -= r;
274                                                 diff += r;
275                                         }
276                                 }
277
278                                 readBuffer = b;
279                                 readBufferOffset = 0;
280                                 readBufferSize = new_size;
281                                 totalRead = 0;
282                                 nextReadCalled = true;
283                         }
284
285                         cnc.NextRead ();
286                 }
287
288                 void WriteCallbackWrapper (IAsyncResult r)
289                 {
290                         WebAsyncResult result = r as WebAsyncResult;
291                         if (result != null && result.AsyncWriteAll)
292                                 return;
293
294                         if (r.AsyncState != null) {
295                                 result = (WebAsyncResult) r.AsyncState;
296                                 result.InnerAsyncResult = r;
297                                 result.DoCallback ();
298                         } else {
299                                 try {
300                                         EndWrite (r);
301                                 } catch {
302                                 }
303                         }
304                 }
305
306                 void ReadCallbackWrapper (IAsyncResult r)
307                 {
308                         WebAsyncResult result;
309                         if (r.AsyncState != null) {
310                                 result = (WebAsyncResult) r.AsyncState;
311                                 result.InnerAsyncResult = r;
312                                 result.DoCallback ();
313                         } else {
314                                 try {
315                                         EndRead (r);
316                                 } catch {
317                                 }
318                         }
319                 }
320
321                 public override int Read (byte [] buffer, int offset, int size)
322                 {
323                         AsyncCallback cb = cb_wrapper;
324                         WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
325                         if (!res.IsCompleted && !res.WaitUntilComplete (ReadTimeout, false)) {
326                                 nextReadCalled = true;
327                                 cnc.Close (true);
328                                 throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
329                         }
330
331                         return EndRead (res);
332                 }
333
334                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
335                                                         AsyncCallback cb, object state)
336                 {
337                         if (!isRead)
338                                 throw new NotSupportedException ("this stream does not allow reading");
339
340                         if (buffer == null)
341                                 throw new ArgumentNullException ("buffer");
342
343                         int length = buffer.Length;
344                         if (offset < 0 || length < offset)
345                                 throw new ArgumentOutOfRangeException ("offset");
346                         if (size < 0 || (length - offset) < size)
347                                 throw new ArgumentOutOfRangeException ("size");
348
349                         lock (locker) {
350                                 pendingReads++;
351                                 pending.Reset ();
352                         }
353
354                         WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
355                         if (totalRead >= contentLength) {
356                                 result.SetCompleted (true, -1);
357                                 result.DoCallback ();
358                                 return result;
359                         }
360                         
361                         int remaining = readBufferSize - readBufferOffset;
362                         if (remaining > 0) {
363                                 int copy = (remaining > size) ? size : remaining;
364                                 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
365                                 readBufferOffset += copy;
366                                 offset += copy;
367                                 size -= copy;
368                                 totalRead += copy;
369                                 if (size == 0 || totalRead >= contentLength) {
370                                         result.SetCompleted (true, copy);
371                                         result.DoCallback ();
372                                         return result;
373                                 }
374                                 result.NBytes = copy;
375                         }
376
377                         if (cb != null)
378                                 cb = cb_wrapper;
379
380                         if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
381                                 size = contentLength - totalRead;
382
383                         if (!read_eof) {
384                                 result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result);
385                         } else {
386                                 result.SetCompleted (true, result.NBytes);
387                                 result.DoCallback ();
388                         }
389                         return result;
390                 }
391
392                 public override int EndRead (IAsyncResult r)
393                 {
394                         WebAsyncResult result = (WebAsyncResult) r;
395                         if (result.EndCalled) {
396                                 int xx = result.NBytes;
397                                 return (xx >= 0) ? xx : 0;
398                         }
399
400                         result.EndCalled = true;
401
402                         if (!result.IsCompleted) {
403                                 int nbytes = -1;
404                                 try {
405                                         nbytes = cnc.EndRead (request, result);
406                                 } catch (Exception exc) {
407                                         lock (locker) {
408                                                 pendingReads--;
409                                                 if (pendingReads == 0)
410                                                         pending.Set ();
411                                         }
412
413                                         nextReadCalled = true;
414                                         cnc.Close (true);
415                                         result.SetCompleted (false, exc);
416                                         result.DoCallback ();
417                                         throw;
418                                 }
419
420                                 if (nbytes < 0) {
421                                         nbytes = 0;
422                                         read_eof = true;
423                                 }
424
425                                 totalRead += nbytes;
426                                 result.SetCompleted (false, nbytes + result.NBytes);
427                                 result.DoCallback ();
428                                 if (nbytes == 0)
429                                         contentLength = totalRead;
430                         }
431
432                         lock (locker) {
433                                 pendingReads--;
434                                 if (pendingReads == 0)
435                                         pending.Set ();
436                         }
437
438                         if (totalRead >= contentLength && !nextReadCalled)
439                                 ReadAll ();
440
441                         int nb = result.NBytes;
442                         return (nb >= 0) ? nb : 0;
443                 }
444
445                 void WriteRequestAsyncCB (IAsyncResult r)
446                 {
447                         WebAsyncResult result = (WebAsyncResult) r.AsyncState;
448                         try {
449                                 cnc.EndWrite2 (request, r);
450                                 result.SetCompleted (false, 0);
451                                 if (!initRead) {
452                                         initRead = true;
453                                         WebConnection.InitRead (cnc);
454                                 }
455                         } catch (Exception e) {
456                                 KillBuffer ();
457                                 nextReadCalled = true;
458                                 cnc.Close (true);
459                                 if (e is System.Net.Sockets.SocketException)
460                                         e = new IOException ("Error writing request", e);
461                                 result.SetCompleted (false, e);
462                         }
463                         complete_request_written = true;
464                         result.DoCallback ();
465                 }
466
467                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
468                                                         AsyncCallback cb, object state)
469                 {
470                         if (request.Aborted)
471                                 throw new WebException ("The request was canceled.", null, WebExceptionStatus.RequestCanceled);
472
473                         if (isRead)
474                                 throw new NotSupportedException ("this stream does not allow writing");
475
476                         if (buffer == null)
477                                 throw new ArgumentNullException ("buffer");
478
479                         int length = buffer.Length;
480                         if (offset < 0 || length < offset)
481                                 throw new ArgumentOutOfRangeException ("offset");
482                         if (size < 0 || (length - offset) < size)
483                                 throw new ArgumentOutOfRangeException ("size");
484
485                         if (sendChunked) {
486                                 lock (locker) {
487                                         pendingWrites++;
488                                         pending.Reset ();
489                                 }
490                         }
491
492                         WebAsyncResult result = new WebAsyncResult (cb, state);
493                         if (!sendChunked)
494                                 CheckWriteOverflow (request.ContentLength, totalWritten, size);
495                         if (allowBuffering && !sendChunked) {
496                                 if (writeBuffer == null)
497                                         writeBuffer = new MemoryStream ();
498                                 writeBuffer.Write (buffer, offset, size);
499                                 totalWritten += size;
500                                 if (request.ContentLength > 0 && totalWritten == request.ContentLength) {
501                                         try {
502                                                 result.AsyncWriteAll = true;
503                                                 result.InnerAsyncResult = WriteRequestAsync (new AsyncCallback (WriteRequestAsyncCB), result);
504                                                 if (result.InnerAsyncResult == null) {
505                                                         if (!result.IsCompleted)
506                                                                 result.SetCompleted (true, 0);
507                                                         result.DoCallback ();
508                                                 }
509                                         } catch (Exception exc) {
510                                                 result.SetCompleted (true, exc);
511                                                 result.DoCallback ();
512                                         }
513                                 } else {
514                                         result.SetCompleted (true, 0);
515                                         result.DoCallback ();
516                                 }
517                                 return result;
518                         }
519
520                         AsyncCallback callback = null;
521                         if (cb != null)
522                                 callback = cb_wrapper;
523
524                         if (sendChunked) {
525                                 WriteRequest ();
526
527                                 string cSize = String.Format ("{0:X}\r\n", size);
528                                 byte [] head = Encoding.ASCII.GetBytes (cSize);
529                                 int chunkSize = 2 + size + head.Length;
530                                 byte [] newBuffer = new byte [chunkSize];
531                                 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
532                                 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
533                                 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
534
535                                 buffer = newBuffer;
536                                 offset = 0;
537                                 size = chunkSize;
538                         }
539
540                         try {
541                                 result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result);
542                         } catch (Exception) {
543                                 if (!IgnoreIOErrors)
544                                         throw;
545                                 result.SetCompleted (true, 0);
546                                 result.DoCallback ();
547                         }
548                         totalWritten += size;
549                         return result;
550                 }
551
552                 void CheckWriteOverflow (long contentLength, long totalWritten, long size)
553                 {
554                         if (contentLength == -1)
555                                 return;
556
557                         long avail = contentLength - totalWritten;
558                         if (size > avail) {
559                                 KillBuffer ();
560                                 nextReadCalled = true;
561                                 cnc.Close (true);
562                                 throw new ProtocolViolationException (
563                                         "The number of bytes to be written is greater than " +
564                                         "the specified ContentLength.");
565                         }
566                 }
567
568                 public override void EndWrite (IAsyncResult r)
569                 {
570                         if (r == null)
571                                 throw new ArgumentNullException ("r");
572
573                         WebAsyncResult result = r as WebAsyncResult;
574                         if (result == null)
575                                 throw new ArgumentException ("Invalid IAsyncResult");
576
577                         if (result.EndCalled)
578                                 return;
579
580                         result.EndCalled = true;
581                         if (result.AsyncWriteAll) {
582                                 result.WaitUntilComplete ();
583                                 if (result.GotException)
584                                         throw result.Exception;
585                                 return;
586                         }
587
588                         if (allowBuffering && !sendChunked)
589                                 return;
590
591                         if (result.GotException)
592                                 throw result.Exception;
593
594                         try {
595                                 cnc.EndWrite2 (request, result.InnerAsyncResult);
596                                 result.SetCompleted (false, 0);
597                                 result.DoCallback ();
598                         } catch (Exception e) {
599                                 if (IgnoreIOErrors)
600                                         result.SetCompleted (false, 0);
601                                 else
602                                         result.SetCompleted (false, e);
603                                 result.DoCallback ();
604                                 if (!IgnoreIOErrors)
605                                         throw;
606                         } finally {
607                                 if (sendChunked) {
608                                         lock (locker) {
609                                                 pendingWrites--;
610                                                 if (pendingWrites == 0)
611                                                         pending.Set ();
612                                         }
613                                 }
614                         }
615                 }
616                 
617                 public override void Write (byte [] buffer, int offset, int size)
618                 {
619                         AsyncCallback cb = cb_wrapper;
620                         WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
621                         if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) {
622                                 KillBuffer ();
623                                 nextReadCalled = true;
624                                 cnc.Close (true);
625                                 throw new IOException ("Write timed out.");
626                         }
627
628                         EndWrite (res);
629                 }
630
631                 public override void Flush ()
632                 {
633                 }
634
635                 internal void SetHeadersAsync (byte[] buffer, WebAsyncResult result)
636                 {
637                         if (headersSent)
638                                 return;
639
640                         headers = buffer;
641                         long cl = request.ContentLength;
642                         string method = request.Method;
643                         bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
644                                                 method == "TRACE");
645                         bool webdav = (method == "PROPFIND" || method == "PROPPATCH" || method == "MKCOL" ||
646                                        method == "COPY" || method == "MOVE" || method == "LOCK" ||
647                                        method == "UNLOCK");
648                         if (sendChunked || cl > -1 || no_writestream || webdav) {
649
650                                 headersSent = true;
651
652                                 try {
653                                         result.InnerAsyncResult = cnc.BeginWrite (request, headers, 0, headers.Length, new AsyncCallback(SetHeadersCB), result);
654                                         if (result.InnerAsyncResult == null) {
655                                                 // when does BeginWrite return null? Is the case when the request is aborted?
656                                                 if (!result.IsCompleted)
657                                                         result.SetCompleted (true, 0);
658                                                 result.DoCallback ();
659                                         }
660                                 } catch (Exception exc) {
661                                         result.SetCompleted (true, exc);
662                                         result.DoCallback ();
663                                 }
664                         }
665                 }
666
667                 void SetHeadersCB (IAsyncResult r)
668                 {
669                         WebAsyncResult result = (WebAsyncResult) r.AsyncState;
670                         result.InnerAsyncResult = null;
671                         try {
672                                 cnc.EndWrite2 (request, r);
673                                 result.SetCompleted (false, 0);
674                                 if (!initRead) {
675                                         initRead = true;
676                                         WebConnection.InitRead (cnc);
677                                 }
678                                 long cl = request.ContentLength;
679                                 if (!sendChunked && cl == 0)
680                                         requestWritten = true;
681                         } catch (WebException e) {
682                                 result.SetCompleted (false, e);
683                         } catch (Exception e) {
684                                 result.SetCompleted (false, new WebException ("Error writing headers", e, WebExceptionStatus.SendFailure));
685                         }
686                         result.DoCallback ();
687                 }
688
689                 internal bool RequestWritten {
690                         get { return requestWritten; }
691                 }
692
693                 IAsyncResult WriteRequestAsync (AsyncCallback cb, object state)
694                 {
695                         requestWritten = true;
696                         byte [] bytes = writeBuffer.GetBuffer ();
697                         int length = (int) writeBuffer.Length;
698                         // Headers already written to the stream
699                         return (length > 0) ? cnc.BeginWrite (request, bytes, 0, length, cb, state) : null;
700                 }
701
702                 internal void WriteRequest ()
703                 {
704                         if (requestWritten)
705                                 return;
706
707                         requestWritten = true;
708                         if (sendChunked)
709                                 return;
710
711                         if (!allowBuffering || writeBuffer == null)
712                                 return;
713
714                         byte [] bytes = writeBuffer.GetBuffer ();
715                         int length = (int) writeBuffer.Length;
716                         if (request.ContentLength != -1 && request.ContentLength < length) {
717                                 nextReadCalled = true;
718                                 cnc.Close (true);
719                                 throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
720                                                         WebExceptionStatus.ServerProtocolViolation, null);
721                         }
722
723                         if (!headersSent) {
724                                 string method = request.Method;
725                                 bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
726                                                         method == "TRACE");
727                                 if (!no_writestream)
728                                         request.InternalContentLength = length;
729
730                                 byte[] requestHeaders = request.GetRequestHeaders ();
731                                 WebAsyncResult ar = new WebAsyncResult (null, null);
732                                 SetHeadersAsync (requestHeaders, ar);
733                                 ar.AsyncWaitHandle.WaitOne ();
734                                 if (ar.Exception != null)
735                                         throw ar.Exception;
736                         }
737
738                         if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
739                                 return;
740                                 
741                         IAsyncResult result = null;
742                         if (length > 0)
743                                 result = cnc.BeginWrite (request, bytes, 0, length, null, null);
744                         
745                         if (!initRead) {
746                                 initRead = true;
747                                 WebConnection.InitRead (cnc);
748                         }
749
750                         if (length > 0) 
751                                 complete_request_written = cnc.EndWrite (request, result);
752                         else
753                                 complete_request_written = true;
754                 }
755
756                 internal void InternalClose ()
757                 {
758                         disposed = true;
759                 }
760
761                 public override void Close ()
762                 {
763                         if (sendChunked) {
764                                 if (disposed)
765                                         return;
766                                 disposed = true;
767                                 pending.WaitOne ();
768                                 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
769                                 string err_msg = null;
770                                 cnc.Write (request, chunk, 0, chunk.Length, ref err_msg);
771                                 return;
772                         }
773
774                         if (isRead) {
775                                 if (!nextReadCalled) {
776                                         CheckComplete ();
777                                         // If we have not read all the contents
778                                         if (!nextReadCalled) {
779                                                 nextReadCalled = true;
780                                                 cnc.Close (true);
781                                         }
782                                 }
783                                 return;
784                         } else if (!allowBuffering) {
785                                 complete_request_written = true;
786                                 if (!initRead) {
787                                         initRead = true;
788                                         WebConnection.InitRead (cnc);
789                                 }
790                                 return;
791                         }
792
793                         if (disposed || requestWritten)
794                                 return;
795
796                         long length = request.ContentLength;
797
798                         if (!sendChunked && length != -1 && totalWritten != length) {
799                                 IOException io = new IOException ("Cannot close the stream until all bytes are written");
800                                 nextReadCalled = true;
801                                 cnc.Close (true);
802                                 throw new WebException ("Request was cancelled.", io, WebExceptionStatus.RequestCanceled);
803                         }
804
805                         // Commented out the next line to fix xamarin bug #1512
806                         //WriteRequest ();
807                         disposed = true;
808                 }
809
810                 internal void KillBuffer ()
811                 {
812                         writeBuffer = null;
813                 }
814
815                 public override long Seek (long a, SeekOrigin b)
816                 {
817                         throw new NotSupportedException ();
818                 }
819                 
820                 public override void SetLength (long a)
821                 {
822                         throw new NotSupportedException ();
823                 }
824                 
825                 public override bool CanSeek {
826                         get { return false; }
827                 }
828
829                 public override bool CanRead {
830                         get { return !disposed && isRead; }
831                 }
832
833                 public override bool CanWrite {
834                         get { return !disposed && !isRead; }
835                 }
836
837                 public override long Length {
838                         get {
839                                 if (!isRead)
840                                         throw new NotSupportedException ();
841                                 return stream_length;
842                         }
843                 }
844
845                 public override long Position {
846                         get { throw new NotSupportedException (); }
847                         set { throw new NotSupportedException (); }
848                 }
849         }
850 }
851