| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- //
- // System.Net.WebConnectionStream
- //
- // Authors:
- // Gonzalo Paniagua Javier ([email protected])
- //
- // (C) 2003 Ximian, Inc (http://www.ximian.com)
- // (C) 2004 Novell, Inc (http://www.novell.com)
- //
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System.IO;
- using System.Text;
- using System.Threading;
- namespace System.Net
- {
- class WebConnectionStream : Stream
- {
- static byte [] crlf = new byte [] { 13, 10 };
- bool isRead;
- WebConnection cnc;
- HttpWebRequest request;
- byte [] readBuffer;
- int readBufferOffset;
- int readBufferSize;
- int contentLength;
- int totalRead;
- bool nextReadCalled;
- int pendingReads;
- int pendingWrites;
- ManualResetEvent pending;
- bool allowBuffering;
- bool sendChunked;
- MemoryStream writeBuffer;
- bool requestWritten;
- byte [] headers;
- bool disposed;
- bool headersSent;
- bool forceCompletion;
- public WebConnectionStream (WebConnection cnc)
- {
- isRead = true;
- pending = new ManualResetEvent (true);
- this.cnc = cnc;
- string clength = cnc.Data.Headers ["Content-Length"];
- if (clength != null && clength != "") {
- try {
- contentLength = Int32.Parse (clength);
- } catch {
- contentLength = Int32.MaxValue;
- }
- } else {
- contentLength = Int32.MaxValue;
- }
- }
- public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
- {
- isRead = false;
- this.cnc = cnc;
- this.request = request;
- allowBuffering = request.InternalAllowBuffering;
- sendChunked = request.SendChunked;
- if (allowBuffering)
- writeBuffer = new MemoryStream ();
- if (sendChunked)
- pending = new ManualResetEvent (true);
- }
- internal bool SendChunked {
- set { sendChunked = value; }
- }
- internal byte [] ReadBuffer {
- set { readBuffer = value; }
- }
- internal int ReadBufferOffset {
- set { readBufferOffset = value;}
- }
-
- internal int ReadBufferSize {
- set { readBufferSize = value; }
- }
-
- internal byte[] WriteBuffer {
- get { return writeBuffer.GetBuffer (); }
- }
- internal int WriteBufferLength {
- get { return (int) writeBuffer.Length; }
- }
- internal void ForceCompletion ()
- {
- forceCompletion = true;
- }
-
- internal void CheckComplete ()
- {
- bool nrc = nextReadCalled;
- if (forceCompletion || (!nrc && readBufferSize - readBufferOffset == contentLength)) {
- nextReadCalled = true;
- cnc.NextRead ();
- }
- }
- internal void ReadAll ()
- {
- if (!isRead || totalRead >= contentLength || nextReadCalled) {
- if (!nextReadCalled) {
- nextReadCalled = true;
- cnc.NextRead ();
- }
- return;
- }
- pending.WaitOne ();
- lock (this) {
- if (totalRead >= contentLength)
- return;
-
- byte [] b = null;
- int diff = readBufferSize - readBufferOffset;
- int new_size;
- if (contentLength == Int32.MaxValue) {
- MemoryStream ms = new MemoryStream ();
- byte [] buffer = null;
- if (readBuffer != null && diff > 0) {
- ms.Write (readBuffer, readBufferOffset, diff);
- if (readBufferSize >= 8192)
- buffer = readBuffer;
- }
- if (buffer == null)
- buffer = new byte [8192];
- int read;
- while ((read = cnc.Read (buffer, 0, buffer.Length)) != 0)
- ms.Write (buffer, 0, read);
- b = ms.GetBuffer ();
- new_size = (int) ms.Length;
- contentLength = new_size;
- } else {
- new_size = contentLength - totalRead;
- b = new byte [new_size];
- if (readBuffer != null && diff > 0) {
- if (diff > new_size)
- diff = new_size;
- Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
- }
-
- int remaining = new_size - diff;
- int r = -1;
- while (remaining > 0 && r != 0) {
- r = cnc.Read (b, diff, remaining);
- remaining -= r;
- diff += r;
- }
- }
- readBuffer = b;
- readBufferOffset = 0;
- readBufferSize = new_size;
- totalRead = 0;
- nextReadCalled = true;
- }
- cnc.NextRead ();
- }
-
- static void CallbackWrapper (IAsyncResult r)
- {
- WebAsyncResult result = (WebAsyncResult) r.AsyncState;
- result.InnerAsyncResult = r;
- result.DoCallback ();
- }
- public override int Read (byte [] buffer, int offset, int size)
- {
- if (!isRead)
- throw new NotSupportedException ("this stream does not allow reading");
- if (totalRead >= contentLength)
- return 0;
- IAsyncResult res = BeginRead (buffer, offset, size, null, null);
- return EndRead (res);
- }
- public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
- AsyncCallback cb, object state)
- {
- if (!isRead)
- throw new NotSupportedException ("this stream does not allow reading");
- if (buffer == null)
- throw new ArgumentNullException ("buffer");
- int length = buffer.Length;
- if (size < 0 || offset < 0 || length < offset || length - offset < size)
- throw new ArgumentOutOfRangeException ();
- lock (this) {
- pendingReads++;
- pending.Reset ();
- }
- WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
- if (totalRead >= contentLength) {
- result.SetCompleted (true, -1);
- result.DoCallback ();
- return result;
- }
-
- int remaining = readBufferSize - readBufferOffset;
- if (remaining > 0) {
- int copy = (remaining > size) ? size : remaining;
- Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
- readBufferOffset += copy;
- offset += copy;
- size -= copy;
- totalRead += copy;
- if (size == 0 || totalRead >= contentLength) {
- result.SetCompleted (true, copy);
- result.DoCallback ();
- return result;
- }
- result.NBytes = copy;
- }
- if (cb != null)
- cb = new AsyncCallback (CallbackWrapper);
- if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
- size = contentLength - totalRead;
- result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result);
- return result;
- }
- public override int EndRead (IAsyncResult r)
- {
- WebAsyncResult result = (WebAsyncResult) r;
- if (!result.IsCompleted) {
- int nbytes = cnc.EndRead (result.InnerAsyncResult);
- bool finished = (nbytes == -1);
- if (finished && result.NBytes > 0)
- nbytes = 0;
- result.SetCompleted (false, nbytes + result.NBytes);
- totalRead += nbytes;
- if (finished || nbytes == 0)
- contentLength = totalRead;
- }
- lock (this) {
- pendingReads--;
- if (pendingReads == 0)
- pending.Set ();
- }
- if (totalRead >= contentLength && !nextReadCalled)
- ReadAll ();
- return result.NBytes;
- }
-
- public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
- AsyncCallback cb, object state)
- {
- if (isRead)
- throw new NotSupportedException ("this stream does not allow writing");
- if (buffer == null)
- throw new ArgumentNullException ("buffer");
- int length = buffer.Length;
- if (size < 0 || offset < 0 || length < offset || length - offset < size)
- throw new ArgumentOutOfRangeException ();
- if (sendChunked) {
- lock (this) {
- pendingWrites++;
- pending.Reset ();
- }
- }
- WebAsyncResult result = new WebAsyncResult (cb, state);
- if (allowBuffering) {
- writeBuffer.Write (buffer, offset, size);
- if (!sendChunked) {
- result.SetCompleted (true, 0);
- result.DoCallback ();
- return result;
- }
- }
- AsyncCallback callback = null;
- if (cb != null)
- callback = new AsyncCallback (CallbackWrapper);
- if (sendChunked) {
- WriteRequest ();
- string cSize = String.Format ("{0:X}\r\n", size);
- byte [] head = Encoding.ASCII.GetBytes (cSize);
- int chunkSize = 2 + size + head.Length;
- byte [] newBuffer = new byte [chunkSize];
- Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
- Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
- Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
- buffer = newBuffer;
- offset = 0;
- size = chunkSize;
- }
- result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result);
- return result;
- }
- public override void EndWrite (IAsyncResult r)
- {
- if (r == null)
- throw new ArgumentNullException ("r");
- if (allowBuffering && !sendChunked)
- return;
- WebAsyncResult result = r as WebAsyncResult;
- if (result == null)
- throw new ArgumentException ("Invalid IAsyncResult");
- if (result.GotException)
- throw result.Exception;
- cnc.EndWrite (result.InnerAsyncResult);
- if (sendChunked) {
- lock (this) {
- pendingWrites--;
- if (pendingWrites == 0)
- pending.Set ();
- }
- }
- }
-
- public override void Write (byte [] buffer, int offset, int size)
- {
- if (isRead)
- throw new NotSupportedException ("This stream does not allow writing");
- IAsyncResult res = BeginWrite (buffer, offset, size, null, null);
- EndWrite (res);
- }
- public override void Flush ()
- {
- }
- internal void SetHeaders (byte [] buffer, int offset, int size)
- {
- if (headersSent)
- return;
- if (!allowBuffering || sendChunked) {
- headersSent = true;
- if (!cnc.Connected)
- throw new WebException ("Not connected", null, WebExceptionStatus.SendFailure, null);
- cnc.Write (buffer, offset, size);
- } else {
- headers = new byte [size];
- Buffer.BlockCopy (buffer, offset, headers, 0, size);
- }
- }
- internal bool RequestWritten {
- get { return requestWritten; }
- }
- internal void WriteRequest ()
- {
- if (requestWritten)
- return;
- if (sendChunked) {
- request.SendRequestHeaders ();
- requestWritten = true;
- return;
- }
- if (!allowBuffering || writeBuffer == null)
- return;
- byte [] bytes = writeBuffer.GetBuffer ();
- int length = (int) writeBuffer.Length;
- if (request.ContentLength != -1 && request.ContentLength < length) {
- throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
- WebExceptionStatus.ServerProtocolViolation, null);
- }
- request.InternalContentLength = length;
- request.SendRequestHeaders ();
- requestWritten = true;
- cnc.Write (headers, 0, headers.Length);
- if (!cnc.Connected)
- throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
- headersSent = true;
- if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
- return;
- cnc.Write (bytes, 0, length);
- }
- internal void InternalClose ()
- {
- disposed = true;
- }
-
- public override void Close ()
- {
- if (sendChunked) {
- pending.WaitOne ();
- byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
- cnc.Write (chunk, 0, chunk.Length);
- return;
- }
- if (isRead || !allowBuffering || disposed)
- return;
- disposed = true;
- long length = request.ContentLength;
- if (length != -1 && length > writeBuffer.Length)
- throw new IOException ("Cannot close the stream until all bytes are written");
- WriteRequest ();
- }
- public override long Seek (long a, SeekOrigin b)
- {
- throw new NotSupportedException ();
- }
-
- public override void SetLength (long a)
- {
- throw new NotSupportedException ();
- }
-
- public override bool CanSeek {
- get { return false; }
- }
- public override bool CanRead {
- get { return isRead; }
- }
- public override bool CanWrite {
- get { return !isRead; }
- }
- public override long Length {
- get { throw new NotSupportedException (); }
- }
- public override long Position {
- get { throw new NotSupportedException (); }
- set { throw new NotSupportedException (); }
- }
- }
- }
|