2003-06-09 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 //
9
10 using System.IO;
11 using System.Threading;
12
13 namespace System.Net
14 {
15         class WebConnectionStream : Stream
16         {
17                 bool isRead;
18                 WebConnection cnc;
19                 HttpWebRequest request;
20                 byte [] readBuffer;
21                 int readBufferOffset;
22                 int readBufferSize;
23                 int contentLength;
24                 int totalRead;
25                 bool nextReadCalled;
26                 int pendingReads;
27                 ManualResetEvent pending;
28                 bool allowBuffering;
29                 bool sendChunked;
30                 MemoryStream writeBuffer;
31                 bool requestWritten;
32                 byte [] headers;
33
34                 public WebConnectionStream (WebConnection cnc)
35                 {
36                         isRead = true;
37                         pending = new ManualResetEvent (true);
38                         this.cnc = cnc;
39                         try {
40                                 contentLength = Int32.Parse (cnc.Data.Headers ["Content-Length"]);
41                         } catch {
42                                 contentLength = Int32.MaxValue;
43                         }
44                 }
45
46                 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
47                 {
48                         isRead = false;
49                         this.cnc = cnc;
50                         this.request = request;
51                         allowBuffering = request.InternalAllowBuffering;
52                         sendChunked = request.SendChunked;
53                         if (allowBuffering)
54                                 writeBuffer = new MemoryStream ();
55                 }
56
57                 internal byte [] ReadBuffer {
58                         set { readBuffer = value; }
59                 }
60
61                 internal int ReadBufferOffset {
62                         set { readBufferOffset = value;}
63                 }
64                 
65                 internal int ReadBufferSize {
66                         set { readBufferSize = value; }
67                 }
68                 
69                 internal void CheckComplete ()
70                 {
71                         if (readBufferSize - readBufferOffset == contentLength) {
72                                 nextReadCalled = true;
73                                 cnc.NextRead ();
74                         }
75                 }
76
77                 internal void ReadAll ()
78                 {
79                         if (!isRead || totalRead >= contentLength || nextReadCalled)
80                                 return;
81
82                         pending.WaitOne ();
83                         lock (this) {
84                                 if (totalRead >= contentLength)
85                                         return;
86                                 
87                                 byte [] b = null;
88                                 int diff = readBufferSize - readBufferOffset;
89                                 int new_size;
90
91                                 if (contentLength == Int32.MaxValue) {
92                                         MemoryStream ms = new MemoryStream ();
93                                         if (readBuffer != null && diff > 0)
94                                                 ms.Write (readBuffer, readBufferOffset, diff);
95
96                                         byte [] buffer = new byte [2048];
97                                         int read;
98                                         while ((read = cnc.Read (buffer, 0, 2048)) != 0)
99                                                 ms.Write (buffer, 0, read);
100
101                                         b = ms.GetBuffer ();
102                                         new_size = (int) ms.Length;
103                                 } else {
104                                         new_size = contentLength - totalRead;
105                                         b = new byte [new_size];
106                                         if (readBuffer != null && diff > 0)
107                                                 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
108                                         
109                                         int remaining = new_size - diff;
110                                         int r = -1;
111                                         while (remaining > 0 && r != 0) {
112                                                 r = cnc.Read (b, diff, remaining);
113                                                 remaining -= r;
114                                                 diff += r;
115                                         }
116                                 }
117
118                                 readBuffer = b;
119                                 readBufferOffset = 0;
120                                 readBufferSize = new_size;
121                                 contentLength = new_size;
122                                 totalRead = 0;
123                                 nextReadCalled = true;
124                         }
125
126                         cnc.NextRead ();
127                 }
128                 
129                 public override int Read (byte [] buffer, int offset, int size)
130                 {
131                         if (!isRead)
132                                 throw new NotSupportedException ("this stream does not allow reading");
133
134                         if (totalRead >= contentLength)
135                                 return 0;
136
137                         IAsyncResult res = BeginRead (buffer, offset, size, null, null);
138                         return EndRead (res);
139                 }
140
141                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
142                                                         AsyncCallback cb, object state)
143                 {
144                         if (!isRead)
145                                 throw new NotSupportedException ("this stream does not allow reading");
146
147                         if (buffer == null)
148                                 throw new ArgumentNullException ("buffer");
149
150                         int length = buffer.Length;
151                         if (size < 0 || offset < 0 || length < offset || length - offset < size)
152                                 throw new ArgumentOutOfRangeException ();
153
154                         lock (this) {
155                                 pendingReads++;
156                                 pending.Reset ();
157                         }
158                         
159                         WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
160                         if (totalRead >= contentLength) {
161                                 result.SetCompleted (true, 0);
162                                 result.DoCallback ();
163                                 return result;
164                         }
165                         
166                         int remaining = readBufferSize - readBufferOffset;
167                         if (remaining > 0) {
168                                 int copy = (remaining > size) ? size : remaining;
169                                 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
170                                 totalRead += copy;
171                                 readBufferOffset += copy;
172                                 offset += copy;
173                                 size -= copy;
174                                 if (size == 0 || totalRead >= contentLength) {
175                                         result.SetCompleted (true, copy);
176                                         result.DoCallback ();
177                                         return result;
178                                 }
179                         }
180
181                         result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, null, null);
182                         return result;
183                 }
184
185                 public override int EndRead (IAsyncResult r)
186                 {
187                         WebAsyncResult result = (WebAsyncResult) r;
188
189                         int nbytes = -1;
190                         if (result.IsCompleted) {
191                                 nbytes = result.NBytes;
192                         } else {
193                                 nbytes = cnc.EndRead (result.InnerAsyncResult);
194                                 lock (this) {
195                                         pendingReads--;
196                                         if (pendingReads == 0)
197                                                 pending.Set ();
198                                 }
199                                 result.SetCompleted (false, nbytes);
200                                 totalRead += nbytes;
201                         }
202
203                         if (totalRead >= contentLength && !nextReadCalled) {
204                                 nextReadCalled = true;
205                                 cnc.NextRead ();
206                         }
207
208                         return nbytes;
209                 }
210                 
211                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
212                                                         AsyncCallback cb, object state)
213                 {
214                         if (isRead)
215                                 throw new NotSupportedException ("this stream does not allow writing");
216
217                         if (buffer == null)
218                                 throw new ArgumentNullException ("buffer");
219
220                         int length = buffer.Length;
221                         if (size < 0 || offset < 0 || length < offset || length - offset < size)
222                                 throw new ArgumentOutOfRangeException ();
223
224                         WebAsyncResult result = new WebAsyncResult (cb, state);
225                         if (allowBuffering) {
226                                 writeBuffer.Write (buffer, offset, size);
227                                 result.SetCompleted (true, 0);
228                                 result.DoCallback ();
229                         } else {
230                                 result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, cb, state);
231                                 if (result.InnerAsyncResult == null)
232                                         throw new WebException ("Aborted");
233                         }
234
235                         return result;
236                 }
237
238                 public override void EndWrite (IAsyncResult r)
239                 {
240                         if (r == null)
241                                 throw new ArgumentNullException ("r");
242
243                         if (allowBuffering)
244                                 return;
245
246                         WebAsyncResult result = r as WebAsyncResult;
247                         if (result == null)
248                                 throw new ArgumentException ("Invalid IAsyncResult");
249
250                         cnc.EndWrite (result.InnerAsyncResult);
251                         return;
252                 }
253                 
254                 public override void Write (byte [] buffer, int offset, int size)
255                 {
256                         if (isRead)
257                                 throw new NotSupportedException ("this stream does not allow writing");
258
259                         IAsyncResult res = BeginWrite (buffer, offset, size, null, null);
260                         EndWrite (res);
261                 }
262
263                 public override void Flush ()
264                 {
265                 }
266
267                 internal void SetHeaders (byte [] buffer, int offset, int size)
268                 {
269                         if (!allowBuffering) {
270                                 Write (buffer, offset, size);
271                         } else {
272                                 headers = new byte [size];
273                                 Buffer.BlockCopy (buffer, offset, headers, 0, size);
274                         }
275                 }
276
277                 internal void WriteRequest ()
278                 {
279                         if (!allowBuffering || writeBuffer == null || requestWritten)
280                                 return;
281
282                         byte [] bytes = writeBuffer.GetBuffer ();
283                         int length = (int) writeBuffer.Length;
284                         if (request.ContentLength != -1 && request.ContentLength < length) {
285                                 throw new ProtocolViolationException ("Specified Content-Length is less than the " +
286                                                                       "number of bytes to write");
287                         }
288
289                         request.InternalContentLength = length;
290                         request.SendRequestHeaders ();
291                         cnc.WaitForContinue (headers, 0, headers.Length);
292                         if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
293                                 return;
294
295                         cnc.Write (bytes, 0, length);
296                         requestWritten = true;
297                         cnc.dataAvailable.Set ();
298                 }
299
300                 public override void Close ()
301                 {
302                         if (!allowBuffering)
303                                 return;
304
305                         // may be ReadAll is isRead?
306                         long length = request.ContentLength;
307                         if (length != -1 && length > writeBuffer.Length)
308                                 throw new IOException ("Cannot close the stream until all bytes are written");
309
310                         WriteRequest ();
311                 }
312
313                 internal void ResetWriteBuffer ()
314                 {
315                         if (!allowBuffering)
316                                 return;
317
318                         writeBuffer = new MemoryStream ();
319                         requestWritten = false;
320                 }
321                 
322                 public override long Seek (long a, SeekOrigin b)
323                 {
324                         throw new NotSupportedException ();
325                 }
326                 
327                 public override void SetLength (long a)
328                 {
329                         throw new NotSupportedException ();
330                 }
331                 
332                 public override bool CanSeek {
333                         get { return false; }
334                 }
335
336                 public override bool CanRead {
337                         get { return isRead && (contentLength == Int32.MaxValue || totalRead < contentLength); }
338                 }
339
340                 public override bool CanWrite {
341                         get { return !isRead; }
342                 }
343
344                 public override long Length {
345                         get { throw new NotSupportedException (); }
346                 }
347
348                 public override long Position {
349                         get { throw new NotSupportedException (); }
350                         set { throw new NotSupportedException (); }
351                 }
352         }
353 }
354