2004-02-26 Gonzalo Paniagua Javier <gonzalo@ximian.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 using System.IO;
12 using System.Text;
13 using System.Threading;
14
15 namespace System.Net
16 {
17         class WebConnectionStream : Stream
18         {
19                 static byte [] crlf = new byte [] { 13, 10 };
20                 bool isRead;
21                 WebConnection cnc;
22                 HttpWebRequest request;
23                 byte [] readBuffer;
24                 int readBufferOffset;
25                 int readBufferSize;
26                 int contentLength;
27                 int totalRead;
28                 bool nextReadCalled;
29                 int pendingReads;
30                 int pendingWrites;
31                 ManualResetEvent pending;
32                 bool allowBuffering;
33                 bool sendChunked;
34                 MemoryStream writeBuffer;
35                 bool requestWritten;
36                 byte [] headers;
37                 bool disposed;
38                 bool headersSent;
39
40                 public WebConnectionStream (WebConnection cnc)
41                 {
42                         isRead = true;
43                         pending = new ManualResetEvent (true);
44                         this.cnc = cnc;
45                         try {
46                                 contentLength = Int32.Parse (cnc.Data.Headers ["Content-Length"]);
47                         } catch {
48                                 contentLength = Int32.MaxValue;
49                         }
50                 }
51
52                 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
53                 {
54                         isRead = false;
55                         this.cnc = cnc;
56                         this.request = request;
57                         allowBuffering = request.InternalAllowBuffering;
58                         sendChunked = request.SendChunked;
59                         if (allowBuffering)
60                                 writeBuffer = new MemoryStream ();
61
62                         if (sendChunked)
63                                 pending = new ManualResetEvent (true);
64                 }
65
66                 internal bool SendChunked {
67                         set { sendChunked = value; }
68                 }
69
70                 internal byte [] ReadBuffer {
71                         set { readBuffer = value; }
72                 }
73
74                 internal int ReadBufferOffset {
75                         set { readBufferOffset = value;}
76                 }
77                 
78                 internal int ReadBufferSize {
79                         set { readBufferSize = value; }
80                 }
81                 
82                 internal byte[] WriteBuffer {
83                         get { return writeBuffer.GetBuffer (); }
84                 }
85
86                 internal int WriteBufferLength {
87                         get { return (int) writeBuffer.Length; }
88                 }
89
90                 internal void CheckComplete ()
91                 {
92                         if (!nextReadCalled && readBufferSize - readBufferOffset == contentLength) {
93                                 nextReadCalled = true;
94                                 cnc.NextRead ();
95                         }
96                 }
97
98                 internal void ReadAll ()
99                 {
100                         if (!isRead || totalRead >= contentLength || nextReadCalled)
101                                 return;
102
103                         pending.WaitOne ();
104                         lock (this) {
105                                 if (totalRead >= contentLength)
106                                         return;
107                                 
108                                 byte [] b = null;
109                                 int diff = readBufferSize - readBufferOffset;
110                                 int new_size;
111
112                                 if (contentLength == Int32.MaxValue) {
113                                         MemoryStream ms = new MemoryStream ();
114                                         if (readBuffer != null && diff > 0)
115                                                 ms.Write (readBuffer, readBufferOffset, diff);
116
117                                         byte [] buffer = new byte [2048];
118                                         int read;
119                                         while ((read = cnc.Read (buffer, 0, 2048)) != 0)
120                                                 ms.Write (buffer, 0, read);
121
122                                         b = ms.GetBuffer ();
123                                         new_size = (int) ms.Length;
124                                         contentLength = new_size;
125                                 } else {
126                                         new_size = contentLength - totalRead;
127                                         b = new byte [new_size];
128                                         if (readBuffer != null && diff > 0)
129                                                 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
130                                         
131                                         int remaining = new_size - diff;
132                                         int r = -1;
133                                         while (remaining > 0 && r != 0) {
134                                                 r = cnc.Read (b, diff, remaining);
135                                                 remaining -= r;
136                                                 diff += r;
137                                         }
138                                 }
139
140                                 readBuffer = b;
141                                 readBufferOffset = 0;
142                                 readBufferSize = new_size;
143                                 totalRead = 0;
144                                 nextReadCalled = true;
145                         }
146
147                         cnc.NextRead ();
148                 }
149                 
150                 static void CallbackWrapper (IAsyncResult r)
151                 {
152                         WebAsyncResult result = (WebAsyncResult) r.AsyncState;
153                         result.InnerAsyncResult = r;
154                         result.DoCallback ();
155                 }
156
157                 public override int Read (byte [] buffer, int offset, int size)
158                 {
159                         if (!isRead)
160                                 throw new NotSupportedException ("this stream does not allow reading");
161
162                         if (totalRead >= contentLength)
163                                 return 0;
164
165                         IAsyncResult res = BeginRead (buffer, offset, size, null, null);
166                         return EndRead (res);
167                 }
168
169                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
170                                                         AsyncCallback cb, object state)
171                 {
172                         if (!isRead)
173                                 throw new NotSupportedException ("this stream does not allow reading");
174
175                         if (buffer == null)
176                                 throw new ArgumentNullException ("buffer");
177
178                         int length = buffer.Length;
179                         if (size < 0 || offset < 0 || length < offset || length - offset < size)
180                                 throw new ArgumentOutOfRangeException ();
181
182                         WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
183                         if (totalRead >= contentLength) {
184                                 result.SetCompleted (true, -1);
185                                 result.DoCallback ();
186                                 return result;
187                         }
188                         
189                         int remaining = readBufferSize - readBufferOffset;
190                         if (remaining > 0) {
191                                 int copy = (remaining > size) ? size : remaining;
192                                 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
193                                 readBufferOffset += copy;
194                                 offset += copy;
195                                 size -= copy;
196                                 totalRead += copy;
197                                 if (size == 0 || totalRead >= contentLength) {
198                                         result.SetCompleted (true, copy);
199                                         result.DoCallback ();
200                                         return result;
201                                 }
202                                 result.NBytes = copy;
203                         }
204
205                         lock (this) {
206                                 pendingReads++;
207                                 pending.Reset ();
208                         }
209
210                         if (cb != null)
211                                 cb = new AsyncCallback (CallbackWrapper);
212
213                         if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
214                                 size = contentLength - totalRead;
215
216                         result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result);
217                         return result;
218                 }
219
220                 public override int EndRead (IAsyncResult r)
221                 {
222                         WebAsyncResult result = (WebAsyncResult) r;
223
224                         if (!result.IsCompleted) {
225                                 int nbytes = cnc.EndRead (result.InnerAsyncResult);
226                                 lock (this) {
227                                         pendingReads--;
228                                         if (pendingReads == 0)
229                                                 pending.Set ();
230                                 }
231
232                                 bool finished = (nbytes == -1);
233                                 if (finished && result.NBytes > 0)
234                                         nbytes = 0;
235
236                                 result.SetCompleted (false, nbytes + result.NBytes);
237                                 totalRead += nbytes;
238                                 if (finished || nbytes == 0)
239                                         contentLength = totalRead;
240                         }
241
242                         if (totalRead >= contentLength && !nextReadCalled) {
243                                 nextReadCalled = true;
244                                 cnc.NextRead ();
245                         }
246
247                         return result.NBytes;
248                 }
249                 
250                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
251                                                         AsyncCallback cb, object state)
252                 {
253                         if (isRead)
254                                 throw new NotSupportedException ("this stream does not allow writing");
255
256                         if (buffer == null)
257                                 throw new ArgumentNullException ("buffer");
258
259                         int length = buffer.Length;
260                         if (size < 0 || offset < 0 || length < offset || length - offset < size)
261                                 throw new ArgumentOutOfRangeException ();
262
263                         if (sendChunked) {
264                                 lock (this) {
265                                         pendingWrites++;
266                                         pending.Reset ();
267                                 }
268                         }
269
270                         WebAsyncResult result = new WebAsyncResult (cb, state);
271                         if (allowBuffering) {
272                                 writeBuffer.Write (buffer, offset, size);
273                                 if (!sendChunked) {
274                                         result.SetCompleted (true, 0);
275                                         result.DoCallback ();
276                                         return result;
277                                 }
278                         }
279
280                         AsyncCallback callback = null;
281                         if (cb != null)
282                                 callback = new AsyncCallback (CallbackWrapper);
283
284                         if (sendChunked) {
285                                 WriteRequest ();
286
287                                 string cSize = String.Format ("{0:X}\r\n", size);
288                                 byte [] head = Encoding.ASCII.GetBytes (cSize);
289                                 int chunkSize = 2 + size + head.Length;
290                                 byte [] newBuffer = new byte [chunkSize];
291                                 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
292                                 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
293                                 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
294
295                                 buffer = newBuffer;
296                                 offset = 0;
297                                 size = chunkSize;
298                         }
299
300                         result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result);
301                         return result;
302                 }
303
304                 public override void EndWrite (IAsyncResult r)
305                 {
306                         if (r == null)
307                                 throw new ArgumentNullException ("r");
308
309                         if (allowBuffering && !sendChunked)
310                                 return;
311
312                         WebAsyncResult result = r as WebAsyncResult;
313                         if (result == null)
314                                 throw new ArgumentException ("Invalid IAsyncResult");
315
316                         if (result.GotException)
317                                 throw result.Exception;
318
319                         cnc.EndWrite (result.InnerAsyncResult);
320                         if (sendChunked) {
321                                 lock (this) {
322                                         pendingWrites--;
323                                         if (pendingWrites == 0)
324                                                 pending.Set ();
325                                 }
326                         }
327                 }
328                 
329                 public override void Write (byte [] buffer, int offset, int size)
330                 {
331                         if (isRead)
332                                 throw new NotSupportedException ("This stream does not allow writing");
333
334                         IAsyncResult res = BeginWrite (buffer, offset, size, null, null);
335                         EndWrite (res);
336                 }
337
338                 public override void Flush ()
339                 {
340                 }
341
342                 internal void SetHeaders (byte [] buffer, int offset, int size)
343                 {
344                         if (headersSent)
345                                 return;
346
347                         if (!allowBuffering || sendChunked) {
348                                 headersSent = true;
349                                 try {
350                                         cnc.Write (buffer, offset, size);
351                                 } catch (IOException) {
352                                         if (cnc.Connected)
353                                                 throw;
354
355                                         if (!cnc.TryReconnect ())
356                                                 throw;
357
358                                         cnc.Write (buffer, offset, size);
359                                 }
360                         } else {
361                                 headers = new byte [size];
362                                 Buffer.BlockCopy (buffer, offset, headers, 0, size);
363                         }
364                 }
365
366                 internal void WriteRequest ()
367                 {
368                         if (requestWritten)
369                                 return;
370
371                         if (sendChunked) {
372                                 request.SendRequestHeaders ();
373                                 requestWritten = true;
374                                 return;
375                         }
376
377                         if (!allowBuffering || writeBuffer == null)
378                                 return;
379
380                         byte [] bytes = writeBuffer.GetBuffer ();
381                         int length = (int) writeBuffer.Length;
382                         if (request.ContentLength != -1 && request.ContentLength < length) {
383                                 throw new ProtocolViolationException ("Specified Content-Length is less than the " +
384                                                                       "number of bytes to write");
385                         }
386
387                         request.InternalContentLength = length;
388                         request.SendRequestHeaders ();
389                         requestWritten = true;
390                         while (true) {
391                                 cnc.Write (headers, 0, headers.Length);
392                                 if (!cnc.Connected) {
393                                         if (!cnc.TryReconnect ())
394                                                 return;
395
396                                         continue;
397                                 }
398                                 headersSent = true;
399
400                                 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
401                                         return;
402
403                                 cnc.Write (bytes, 0, length);
404                                 if (!cnc.Connected && cnc.TryReconnect ())
405                                         continue;
406
407                                 break;
408                         }
409                 }
410
411                 internal void InternalClose ()
412                 {
413                         disposed = true;
414                 }
415                 
416                 public override void Close ()
417                 {
418                         if (sendChunked) {
419                                 pending.WaitOne ();
420                                 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
421                                 cnc.Write (chunk, 0, chunk.Length);
422                                 return;
423                         }
424
425                         if (isRead || !allowBuffering || disposed)
426                                 return;
427
428                         disposed = true;
429
430                         long length = request.ContentLength;
431                         if (length != -1 && length > writeBuffer.Length)
432                                 throw new IOException ("Cannot close the stream until all bytes are written");
433
434                         WriteRequest ();
435                 }
436
437                 internal void ResetWriteBuffer ()
438                 {
439                         if (!allowBuffering)
440                                 return;
441
442                         writeBuffer = new MemoryStream ();
443                         requestWritten = false;
444                         headersSent = false;
445                 }
446                 
447                 public override long Seek (long a, SeekOrigin b)
448                 {
449                         throw new NotSupportedException ();
450                 }
451                 
452                 public override void SetLength (long a)
453                 {
454                         throw new NotSupportedException ();
455                 }
456                 
457                 public override bool CanSeek {
458                         get { return false; }
459                 }
460
461                 public override bool CanRead {
462                         get { return isRead && (contentLength == Int32.MaxValue || totalRead < contentLength); }
463                 }
464
465                 public override bool CanWrite {
466                         get { return !isRead; }
467                 }
468
469                 public override long Length {
470                         get { throw new NotSupportedException (); }
471                 }
472
473                 public override long Position {
474                         get { throw new NotSupportedException (); }
475                         set { throw new NotSupportedException (); }
476                 }
477         }
478 }
479