**** Merged from MCS ****
[mono-project.git] / mcs / class / System / System.Net / WebConnectionStream.cs
blob8b08373614839e2d2823167b0c5cca73219512a0
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 //
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.
32 using System.IO;
33 using System.Text;
34 using System.Threading;
36 namespace System.Net
38 class WebConnectionStream : Stream
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 bool forceCompletion;
62 public WebConnectionStream (WebConnection cnc)
64 isRead = true;
65 pending = new ManualResetEvent (true);
66 this.cnc = cnc;
67 string clength = cnc.Data.Headers ["Content-Length"];
68 if (clength != null && clength != "") {
69 try {
70 contentLength = Int32.Parse (clength);
71 } catch {
72 contentLength = Int32.MaxValue;
74 } else {
75 contentLength = Int32.MaxValue;
79 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
81 isRead = false;
82 this.cnc = cnc;
83 this.request = request;
84 allowBuffering = request.InternalAllowBuffering;
85 sendChunked = request.SendChunked;
86 if (allowBuffering)
87 writeBuffer = new MemoryStream ();
89 if (sendChunked)
90 pending = new ManualResetEvent (true);
93 internal bool SendChunked {
94 set { sendChunked = value; }
97 internal byte [] ReadBuffer {
98 set { readBuffer = value; }
101 internal int ReadBufferOffset {
102 set { readBufferOffset = value;}
105 internal int ReadBufferSize {
106 set { readBufferSize = value; }
109 internal byte[] WriteBuffer {
110 get { return writeBuffer.GetBuffer (); }
113 internal int WriteBufferLength {
114 get { return (int) writeBuffer.Length; }
117 internal void ForceCompletion ()
119 forceCompletion = true;
122 internal void CheckComplete ()
124 bool nrc = nextReadCalled;
125 if (forceCompletion || (!nrc && readBufferSize - readBufferOffset == contentLength)) {
126 nextReadCalled = true;
127 cnc.NextRead ();
131 internal void ReadAll ()
133 if (!isRead || totalRead >= contentLength || nextReadCalled)
134 return;
136 pending.WaitOne ();
137 lock (this) {
138 if (totalRead >= contentLength)
139 return;
141 byte [] b = null;
142 int diff = readBufferSize - readBufferOffset;
143 int new_size;
145 if (contentLength == Int32.MaxValue) {
146 MemoryStream ms = new MemoryStream ();
147 if (readBuffer != null && diff > 0)
148 ms.Write (readBuffer, readBufferOffset, diff);
150 byte [] buffer = new byte [2048];
151 int read;
152 while ((read = cnc.Read (buffer, 0, 2048)) != 0)
153 ms.Write (buffer, 0, read);
155 b = ms.GetBuffer ();
156 new_size = (int) ms.Length;
157 contentLength = new_size;
158 } else {
159 new_size = contentLength - totalRead;
160 b = new byte [new_size];
161 if (readBuffer != null && diff > 0) {
162 if (diff > new_size)
163 diff = new_size;
165 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
168 int remaining = new_size - diff;
169 int r = -1;
170 while (remaining > 0 && r != 0) {
171 r = cnc.Read (b, diff, remaining);
172 remaining -= r;
173 diff += r;
177 readBuffer = b;
178 readBufferOffset = 0;
179 readBufferSize = new_size;
180 totalRead = 0;
181 nextReadCalled = true;
184 cnc.NextRead ();
187 static void CallbackWrapper (IAsyncResult r)
189 WebAsyncResult result = (WebAsyncResult) r.AsyncState;
190 result.InnerAsyncResult = r;
191 result.DoCallback ();
194 public override int Read (byte [] buffer, int offset, int size)
196 if (!isRead)
197 throw new NotSupportedException ("this stream does not allow reading");
199 if (totalRead >= contentLength)
200 return 0;
202 IAsyncResult res = BeginRead (buffer, offset, size, null, null);
203 return EndRead (res);
206 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
207 AsyncCallback cb, object state)
209 if (!isRead)
210 throw new NotSupportedException ("this stream does not allow reading");
212 if (buffer == null)
213 throw new ArgumentNullException ("buffer");
215 int length = buffer.Length;
216 if (size < 0 || offset < 0 || length < offset || length - offset < size)
217 throw new ArgumentOutOfRangeException ();
219 WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
220 if (totalRead >= contentLength) {
221 result.SetCompleted (true, -1);
222 result.DoCallback ();
223 return result;
226 int remaining = readBufferSize - readBufferOffset;
227 if (remaining > 0) {
228 int copy = (remaining > size) ? size : remaining;
229 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
230 readBufferOffset += copy;
231 offset += copy;
232 size -= copy;
233 totalRead += copy;
234 if (size == 0 || totalRead >= contentLength) {
235 result.SetCompleted (true, copy);
236 result.DoCallback ();
237 return result;
239 result.NBytes = copy;
242 lock (this) {
243 pendingReads++;
244 pending.Reset ();
247 if (cb != null)
248 cb = new AsyncCallback (CallbackWrapper);
250 if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
251 size = contentLength - totalRead;
253 result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result);
254 return result;
257 public override int EndRead (IAsyncResult r)
259 WebAsyncResult result = (WebAsyncResult) r;
261 if (!result.IsCompleted) {
262 int nbytes = cnc.EndRead (result.InnerAsyncResult);
263 lock (this) {
264 pendingReads--;
265 if (pendingReads == 0)
266 pending.Set ();
269 bool finished = (nbytes == -1);
270 if (finished && result.NBytes > 0)
271 nbytes = 0;
273 result.SetCompleted (false, nbytes + result.NBytes);
274 totalRead += nbytes;
275 if (finished || nbytes == 0)
276 contentLength = totalRead;
279 if (totalRead >= contentLength && !nextReadCalled) {
280 nextReadCalled = true;
281 cnc.NextRead ();
284 return result.NBytes;
287 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
288 AsyncCallback cb, object state)
290 if (isRead)
291 throw new NotSupportedException ("this stream does not allow writing");
293 if (buffer == null)
294 throw new ArgumentNullException ("buffer");
296 int length = buffer.Length;
297 if (size < 0 || offset < 0 || length < offset || length - offset < size)
298 throw new ArgumentOutOfRangeException ();
300 if (sendChunked) {
301 lock (this) {
302 pendingWrites++;
303 pending.Reset ();
307 WebAsyncResult result = new WebAsyncResult (cb, state);
308 if (allowBuffering) {
309 writeBuffer.Write (buffer, offset, size);
310 if (!sendChunked) {
311 result.SetCompleted (true, 0);
312 result.DoCallback ();
313 return result;
317 AsyncCallback callback = null;
318 if (cb != null)
319 callback = new AsyncCallback (CallbackWrapper);
321 if (sendChunked) {
322 WriteRequest ();
324 string cSize = String.Format ("{0:X}\r\n", size);
325 byte [] head = Encoding.ASCII.GetBytes (cSize);
326 int chunkSize = 2 + size + head.Length;
327 byte [] newBuffer = new byte [chunkSize];
328 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
329 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
330 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
332 buffer = newBuffer;
333 offset = 0;
334 size = chunkSize;
337 result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result);
338 return result;
341 public override void EndWrite (IAsyncResult r)
343 if (r == null)
344 throw new ArgumentNullException ("r");
346 if (allowBuffering && !sendChunked)
347 return;
349 WebAsyncResult result = r as WebAsyncResult;
350 if (result == null)
351 throw new ArgumentException ("Invalid IAsyncResult");
353 if (result.GotException)
354 throw result.Exception;
356 cnc.EndWrite (result.InnerAsyncResult);
357 if (sendChunked) {
358 lock (this) {
359 pendingWrites--;
360 if (pendingWrites == 0)
361 pending.Set ();
366 public override void Write (byte [] buffer, int offset, int size)
368 if (isRead)
369 throw new NotSupportedException ("This stream does not allow writing");
371 IAsyncResult res = BeginWrite (buffer, offset, size, null, null);
372 EndWrite (res);
375 public override void Flush ()
379 internal void SetHeaders (byte [] buffer, int offset, int size)
381 if (headersSent)
382 return;
384 if (!allowBuffering || sendChunked) {
385 headersSent = true;
386 try {
387 cnc.Write (buffer, offset, size);
388 } catch (IOException) {
389 if (cnc.Connected)
390 throw;
392 if (!cnc.TryReconnect ())
393 throw;
395 cnc.Write (buffer, offset, size);
397 } else {
398 headers = new byte [size];
399 Buffer.BlockCopy (buffer, offset, headers, 0, size);
403 internal void WriteRequest ()
405 if (requestWritten)
406 return;
408 if (sendChunked) {
409 request.SendRequestHeaders ();
410 requestWritten = true;
411 return;
414 if (!allowBuffering || writeBuffer == null)
415 return;
417 byte [] bytes = writeBuffer.GetBuffer ();
418 int length = (int) writeBuffer.Length;
419 if (request.ContentLength != -1 && request.ContentLength < length) {
420 throw new ProtocolViolationException ("Specified Content-Length is less than the " +
421 "number of bytes to write");
424 request.InternalContentLength = length;
425 request.SendRequestHeaders ();
426 requestWritten = true;
427 while (true) {
428 cnc.Write (headers, 0, headers.Length);
429 if (!cnc.Connected) {
430 if (!cnc.TryReconnect ())
431 return;
433 continue;
435 headersSent = true;
437 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
438 return;
440 cnc.Write (bytes, 0, length);
441 if (!cnc.Connected && cnc.TryReconnect ())
442 continue;
444 break;
448 internal void InternalClose ()
450 disposed = true;
453 public override void Close ()
455 if (sendChunked) {
456 pending.WaitOne ();
457 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
458 cnc.Write (chunk, 0, chunk.Length);
459 return;
462 if (isRead || !allowBuffering || disposed)
463 return;
465 disposed = true;
467 long length = request.ContentLength;
468 if (length != -1 && length > writeBuffer.Length)
469 throw new IOException ("Cannot close the stream until all bytes are written");
471 WriteRequest ();
474 public override long Seek (long a, SeekOrigin b)
476 throw new NotSupportedException ();
479 public override void SetLength (long a)
481 throw new NotSupportedException ();
484 public override bool CanSeek {
485 get { return false; }
488 public override bool CanRead {
489 get { return isRead; }
492 public override bool CanWrite {
493 get { return !isRead; }
496 public override long Length {
497 get { throw new NotSupportedException (); }
500 public override long Position {
501 get { throw new NotSupportedException (); }
502 set { throw new NotSupportedException (); }