2009-01-26 Gonzalo Paniagua Javier <gonzalo@novell.com>
[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 contentLength;
48                 int totalRead;
49                 bool nextReadCalled;
50                 int pendingReads;
51                 int pendingWrites;
52                 ManualResetEvent pending;
53                 bool allowBuffering;
54                 bool sendChunked;
55                 MemoryStream writeBuffer;
56                 bool requestWritten;
57                 byte [] headers;
58                 bool disposed;
59                 bool headersSent;
60                 object locker = new object ();
61                 bool initRead;
62                 bool read_eof;
63                 bool complete_request_written;
64                 long max_buffer_size;
65
66                 public WebConnectionStream (WebConnection cnc)
67                 {
68                         isRead = true;
69                         pending = new ManualResetEvent (true);
70                         this.request = cnc.Data.request;
71                         this.cnc = cnc;
72                         string contentType = cnc.Data.Headers ["Transfer-Encoding"];
73                         bool chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
74                         string clength = cnc.Data.Headers ["Content-Length"];
75                         if (!chunkedRead && clength != null && clength != "") {
76
77                                 try {
78                                         contentLength = Int32.Parse (clength);
79                                         if (contentLength == 0 && !IsNtlmAuth ()) {
80                                                 ReadAll ();
81                                         }
82                                 } catch {
83                                         contentLength = Int32.MaxValue;
84                                 }
85                         } else {
86                                 contentLength = Int32.MaxValue;
87                         }
88                 }
89
90                 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
91                 {
92                         isRead = false;
93                         this.cnc = cnc;
94                         this.request = request;
95                         allowBuffering = request.InternalAllowBuffering;
96                         sendChunked = request.SendChunked;
97                         if (allowBuffering) {
98                                 writeBuffer = new MemoryStream ();
99                                 max_buffer_size = request.ContentLength;
100                         } else {
101                                 max_buffer_size = -1;
102                         }
103
104                         if (sendChunked)
105                                 pending = new ManualResetEvent (true);
106                 }
107
108                 bool IsNtlmAuth ()
109                 {
110                         bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address));
111                         string header_name = (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate";
112                         string authHeader = cnc.Data.Headers [header_name];
113                         return (authHeader != null && authHeader.IndexOf ("NTLM") != -1);
114                 }
115
116                 internal void CheckResponseInBuffer ()
117                 {
118                         if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) {
119                                 if (!IsNtlmAuth ())
120                                         ReadAll ();
121                         }
122                 }
123
124                 internal WebConnection Connection {
125                         get { return cnc; }
126                 }
127 #if NET_2_0
128                 public override bool CanTimeout {
129                         get { return true; }
130                 }
131 #endif
132
133                 internal bool CompleteRequestWritten {
134                         get { return complete_request_written; }
135                 }
136
137                 internal bool SendChunked {
138                         set { sendChunked = value; }
139                 }
140
141                 internal byte [] ReadBuffer {
142                         set { readBuffer = value; }
143                 }
144
145                 internal int ReadBufferOffset {
146                         set { readBufferOffset = value;}
147                 }
148                 
149                 internal int ReadBufferSize {
150                         set { readBufferSize = value; }
151                 }
152                 
153                 internal byte[] WriteBuffer {
154                         get { return writeBuffer.GetBuffer (); }
155                 }
156
157                 internal int WriteBufferLength {
158                         get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); }
159                 }
160
161                 internal void ForceCompletion ()
162                 {
163                         if (!nextReadCalled) {
164                                 nextReadCalled = true;
165                                 cnc.NextRead ();
166                         }
167                 }
168                 
169                 internal void CheckComplete ()
170                 {
171                         bool nrc = nextReadCalled;
172                         if (!nrc && readBufferSize - readBufferOffset == contentLength) {
173                                 nextReadCalled = true;
174                                 cnc.NextRead ();
175                         }
176                 }
177
178                 internal void ReadAll ()
179                 {
180                         if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
181                                 if (isRead && !nextReadCalled) {
182                                         nextReadCalled = true;
183                                         cnc.NextRead ();
184                                 }
185                                 return;
186                         }
187
188                         pending.WaitOne ();
189                         lock (locker) {
190                                 if (totalRead >= contentLength)
191                                         return;
192                                 
193                                 byte [] b = null;
194                                 int diff = readBufferSize - readBufferOffset;
195                                 int new_size;
196
197                                 if (contentLength == Int32.MaxValue) {
198                                         MemoryStream ms = new MemoryStream ();
199                                         byte [] buffer = null;
200                                         if (readBuffer != null && diff > 0) {
201                                                 ms.Write (readBuffer, readBufferOffset, diff);
202                                                 if (readBufferSize >= 8192)
203                                                         buffer = readBuffer;
204                                         }
205
206                                         if (buffer == null)
207                                                 buffer = new byte [8192];
208
209                                         int read;
210                                         while ((read = cnc.Read (buffer, 0, buffer.Length)) != 0)
211                                                 ms.Write (buffer, 0, read);
212
213                                         b = ms.GetBuffer ();
214                                         new_size = (int) ms.Length;
215                                         contentLength = new_size;
216                                 } else {
217                                         new_size = contentLength - totalRead;
218                                         b = new byte [new_size];
219                                         if (readBuffer != null && diff > 0) {
220                                                 if (diff > new_size)
221                                                         diff = new_size;
222
223                                                 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
224                                         }
225                                         
226                                         int remaining = new_size - diff;
227                                         int r = -1;
228                                         while (remaining > 0 && r != 0) {
229                                                 r = cnc.Read (b, diff, remaining);
230                                                 remaining -= r;
231                                                 diff += r;
232                                         }
233                                 }
234
235                                 readBuffer = b;
236                                 readBufferOffset = 0;
237                                 readBufferSize = new_size;
238                                 totalRead = 0;
239                                 nextReadCalled = true;
240                         }
241
242                         cnc.NextRead ();
243                 }
244
245                 void WriteCallbackWrapper (IAsyncResult r)
246                 {
247                         WebAsyncResult result;
248                         if (r.AsyncState != null) {
249                                 result = (WebAsyncResult) r.AsyncState;
250                                 result.InnerAsyncResult = r;
251                                 result.DoCallback ();
252                         } else {
253                                 EndWrite (r);
254                         }
255                 }
256
257                 void ReadCallbackWrapper (IAsyncResult r)
258                 {
259                         WebAsyncResult result;
260                         if (r.AsyncState != null) {
261                                 result = (WebAsyncResult) r.AsyncState;
262                                 result.InnerAsyncResult = r;
263                                 result.DoCallback ();
264                         } else {
265                                 EndRead (r);
266                         }
267                 }
268
269                 public override int Read (byte [] buffer, int offset, int size)
270                 {
271                         if (!isRead)
272                                 throw new NotSupportedException ("this stream does not allow reading");
273
274                         if (totalRead >= contentLength)
275                                 return 0;
276
277                         AsyncCallback cb = new AsyncCallback (ReadCallbackWrapper);
278                         WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
279                         if (!res.IsCompleted && !res.WaitUntilComplete (request.ReadWriteTimeout, false)) {
280                                 nextReadCalled = true;
281                                 cnc.Close (true);
282                                 throw new WebException ("The operation has timed out.",
283                                         WebExceptionStatus.Timeout);
284                         }
285
286                         return EndRead (res);
287                 }
288
289                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
290                                                         AsyncCallback cb, object state)
291                 {
292                         if (!isRead)
293                                 throw new NotSupportedException ("this stream does not allow reading");
294
295                         if (buffer == null)
296                                 throw new ArgumentNullException ("buffer");
297
298                         int length = buffer.Length;
299                         if (size < 0 || offset < 0 || length < offset || length - offset < size)
300                                 throw new ArgumentOutOfRangeException ();
301
302                         lock (locker) {
303                                 pendingReads++;
304                                 pending.Reset ();
305                         }
306
307                         WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
308                         if (totalRead >= contentLength) {
309                                 result.SetCompleted (true, -1);
310                                 result.DoCallback ();
311                                 return result;
312                         }
313                         
314                         int remaining = readBufferSize - readBufferOffset;
315                         if (remaining > 0) {
316                                 int copy = (remaining > size) ? size : remaining;
317                                 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
318                                 readBufferOffset += copy;
319                                 offset += copy;
320                                 size -= copy;
321                                 totalRead += copy;
322                                 if (size == 0 || totalRead >= contentLength) {
323                                         result.SetCompleted (true, copy);
324                                         result.DoCallback ();
325                                         return result;
326                                 }
327                                 result.NBytes = copy;
328                         }
329
330                         if (cb != null)
331                                 cb = new AsyncCallback (ReadCallbackWrapper);
332
333                         if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
334                                 size = contentLength - totalRead;
335
336                         if (!read_eof) {
337                                 result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result);
338                         } else {
339                                 result.SetCompleted (true, result.NBytes);
340                                 result.DoCallback ();
341                         }
342                         return result;
343                 }
344
345                 public override int EndRead (IAsyncResult r)
346                 {
347                         WebAsyncResult result = (WebAsyncResult) r;
348                         if (result.EndCalled) {
349                                 int xx = result.NBytes;
350                                 return (xx >= 0) ? xx : 0;
351                         }
352
353                         result.EndCalled = true;
354
355                         if (!result.IsCompleted) {
356                                 int nbytes = -1;
357                                 try {
358                                         nbytes = cnc.EndRead (result);
359                                 } catch (Exception exc) {
360                                         lock (locker) {
361                                                 pendingReads--;
362                                                 if (pendingReads == 0)
363                                                         pending.Set ();
364                                         }
365
366                                         nextReadCalled = true;
367                                         cnc.Close (true);
368                                         result.SetCompleted (false, exc);
369                                         throw;
370                                 }
371
372                                 if (nbytes < 0) {
373                                         nbytes = 0;
374                                         read_eof = true;
375                                 }
376
377                                 totalRead += nbytes;
378                                 result.SetCompleted (false, nbytes + result.NBytes);
379                                 result.DoCallback ();
380                                 if (nbytes == 0)
381                                         contentLength = totalRead;
382                         }
383
384                         lock (locker) {
385                                 pendingReads--;
386                                 if (pendingReads == 0)
387                                         pending.Set ();
388                         }
389
390                         if (totalRead >= contentLength && !nextReadCalled)
391                                 ReadAll ();
392
393                         int nb = result.NBytes;
394                         return (nb >= 0) ? nb : 0;
395                 }
396                 
397                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
398                                                         AsyncCallback cb, object state)
399                 {
400                         if (isRead)
401                                 throw new NotSupportedException ("this stream does not allow writing");
402
403                         if (buffer == null)
404                                 throw new ArgumentNullException ("buffer");
405
406                         int length = buffer.Length;
407                         if (size < 0 || offset < 0 || length < offset || length - offset < size)
408                                 throw new ArgumentOutOfRangeException ();
409
410                         if (sendChunked) {
411                                 lock (locker) {
412                                         pendingWrites++;
413                                         pending.Reset ();
414                                 }
415                         }
416
417                         WebAsyncResult result = new WebAsyncResult (cb, state);
418                         if (allowBuffering) {
419                                 if (max_buffer_size >= 0) {
420                                         long avail = max_buffer_size - writeBuffer.Length;
421                                         if (size > avail) {
422                                                 if (requestWritten)
423                                                         throw new ProtocolViolationException (
424                                                         "The number of bytes to be written is greater than " +
425                                                         "the specified ContentLength.");
426                                         }
427                                 }
428                                 writeBuffer.Write (buffer, offset, size);
429                                 if (!sendChunked) {
430                                         result.SetCompleted (true, 0);
431                                         result.DoCallback ();
432                                         return result;
433                                 }
434                         }
435
436                         AsyncCallback callback = null;
437                         if (cb != null)
438                                 callback = new AsyncCallback (WriteCallbackWrapper);
439
440                         if (sendChunked) {
441                                 WriteRequest ();
442
443                                 string cSize = String.Format ("{0:X}\r\n", size);
444                                 byte [] head = Encoding.ASCII.GetBytes (cSize);
445                                 int chunkSize = 2 + size + head.Length;
446                                 byte [] newBuffer = new byte [chunkSize];
447                                 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
448                                 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
449                                 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
450
451                                 buffer = newBuffer;
452                                 offset = 0;
453                                 size = chunkSize;
454                         }
455
456                         result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result);
457                         return result;
458                 }
459
460                 public override void EndWrite (IAsyncResult r)
461                 {
462                         if (r == null)
463                                 throw new ArgumentNullException ("r");
464
465                         WebAsyncResult result = r as WebAsyncResult;
466                         if (result == null)
467                                 throw new ArgumentException ("Invalid IAsyncResult");
468
469                         if (result.EndCalled)
470                                 return;
471
472                         result.EndCalled = true;
473
474                         if (allowBuffering && !sendChunked)
475                                 return;
476
477                         if (result.GotException)
478                                 throw result.Exception;
479
480                         try { 
481                                 cnc.EndWrite (result.InnerAsyncResult);
482                                 result.SetCompleted (false, 0);
483                         } catch (Exception e) {
484                                 result.SetCompleted (false, e);
485                         }
486
487                         if (sendChunked) {
488                                 lock (locker) {
489                                         pendingWrites--;
490                                         if (pendingWrites == 0)
491                                                 pending.Set ();
492                                 }
493                         }
494                 }
495                 
496                 public override void Write (byte [] buffer, int offset, int size)
497                 {
498                         if (isRead)
499                                 throw new NotSupportedException ("This stream does not allow writing");
500
501                         AsyncCallback cb = new AsyncCallback (WriteCallbackWrapper);
502                         WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
503                         if (!res.IsCompleted && !res.WaitUntilComplete (request.ReadWriteTimeout, false)) {
504                                 nextReadCalled = true;
505                                 cnc.Close (true);
506                                 throw new IOException ("Write timed out.");
507                         }
508
509                         EndWrite (res);
510                 }
511
512                 public override void Flush ()
513                 {
514                 }
515
516                 internal void SetHeaders (byte [] buffer, int offset, int size)
517                 {
518                         if (headersSent)
519                                 return;
520
521                         if (!allowBuffering || sendChunked) {
522                                 headersSent = true;
523                                 if (!cnc.Connected)
524                                         throw new WebException ("Not connected", null, WebExceptionStatus.SendFailure, null);
525
526                                 
527                                 if (!cnc.Write (buffer, offset, size))
528                                         throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
529
530                                 if (!initRead) {
531                                         initRead = true;
532                                         WebConnection.InitRead (cnc);
533                                 }
534                         } else {
535                                 headers = new byte [size];
536                                 Buffer.BlockCopy (buffer, offset, headers, 0, size);
537                         }
538                 }
539
540                 internal bool RequestWritten {
541                         get { return requestWritten; }
542                 }
543
544                 internal void WriteRequest ()
545                 {
546                         if (requestWritten)
547                                 return;
548
549                         if (sendChunked) {
550                                 request.SendRequestHeaders ();
551                                 requestWritten = true;
552                                 return;
553                         }
554
555                         if (!allowBuffering || writeBuffer == null)
556                                 return;
557
558                         byte [] bytes = writeBuffer.GetBuffer ();
559                         int length = (int) writeBuffer.Length;
560                         if (request.ContentLength != -1 && request.ContentLength < length) {
561                                 throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
562                                                         WebExceptionStatus.ServerProtocolViolation, null);
563                         }
564
565                         request.InternalContentLength = length;
566                         request.SendRequestHeaders ();
567                         requestWritten = true;
568
569                         //
570                         // For small requests, make a copy, it will reduce the traffic, for large
571                         // requests, the NoDelay bit on the socket should take effect (set in WebConnection).
572                         //
573                         if (headers.Length + length < 8192){
574                                 byte[] b = new byte [headers.Length + length];
575
576                                 Buffer.BlockCopy (headers, 0, b, 0, headers.Length);
577                                 Buffer.BlockCopy (bytes, 0, b, headers.Length, length);
578                                 
579                                 if (!cnc.Write (b, 0, b.Length))
580                                         throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
581                                 
582                                 headersSent = true;
583                                 complete_request_written = true;
584                                 
585                                 if (!initRead) {
586                                         initRead = true;
587                                         WebConnection.InitRead (cnc);
588                                 }                               
589                         } else {
590                                 if (!cnc.Write (headers, 0, headers.Length))
591                                         throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
592                                 
593                                 headersSent = true;
594                                 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
595                                         return;
596                                 
597                                 IAsyncResult result = null;
598                                 if (length > 0)
599                                         result = cnc.BeginWrite (bytes, 0, length, null, null);
600                                 
601                                 if (!initRead) {
602                                         initRead = true;
603                                         WebConnection.InitRead (cnc);
604                                 }
605                                 
606                                 if (length > 0) 
607                                         complete_request_written = cnc.EndWrite (result);
608                                 else
609                                         complete_request_written = true;
610                         }
611                 }
612
613                 internal void InternalClose ()
614                 {
615                         disposed = true;
616                 }
617
618                 internal void ForceCloseConnection ()
619                 {
620                         if (!disposed) {
621                                 disposed = true;
622                                 cnc.Close (true);
623                         }
624                 }
625
626                 public override void Close ()
627                 {
628                         if (sendChunked) {
629                                 pending.WaitOne ();
630                                 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
631                                 cnc.Write (chunk, 0, chunk.Length);
632                                 return;
633                         }
634
635                         if (isRead) {
636                                 if (!nextReadCalled) {
637                                         CheckComplete ();
638                                         // If we have not read all the contents
639                                         if (!nextReadCalled) {
640                                                 nextReadCalled = true;
641                                                 cnc.Close (true);
642                                         }
643                                 }
644                                 return;
645                         } else if (!allowBuffering) {
646                                 complete_request_written = true;
647                                 if (!initRead) {
648                                         initRead = true;
649                                         WebConnection.InitRead (cnc);
650                                 }
651                                 return;
652                         }
653
654                         if (disposed)
655                                 return;
656
657                         long length = request.ContentLength;
658                         if (length != -1 && length > writeBuffer.Length)
659                                 throw new IOException ("Cannot close the stream until all bytes are written");
660
661                         WriteRequest ();
662                         disposed = true;
663                 }
664
665                 internal void KillBuffer ()
666                 {
667                         writeBuffer = null;
668                 }
669
670                 public override long Seek (long a, SeekOrigin b)
671                 {
672                         throw new NotSupportedException ();
673                 }
674                 
675                 public override void SetLength (long a)
676                 {
677                         throw new NotSupportedException ();
678                 }
679                 
680                 public override bool CanSeek {
681                         get { return false; }
682                 }
683
684                 public override bool CanRead {
685                         get { return isRead; }
686                 }
687
688                 public override bool CanWrite {
689                         get { return !isRead; }
690                 }
691
692                 public override long Length {
693                         get { throw new NotSupportedException (); }
694                 }
695
696                 public override long Position {
697                         get { throw new NotSupportedException (); }
698                         set { throw new NotSupportedException (); }
699                 }
700         }
701 }
702