2 // System.Net.WebConnection
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System
.Collections
;
33 using System
.Net
.Sockets
;
34 using System
.Security
.Cryptography
.X509Certificates
;
36 using System
.Threading
;
37 using System
.Diagnostics
;
38 using Mono
.Net
.Security
;
55 internal Socket socket
;
56 object socketLock
= new object ();
57 IWebConnectionState state
;
58 WebExceptionStatus status
;
59 WaitCallback initConn
;
62 static AsyncCallback readDoneDelegate
= new AsyncCallback (ReadDone
);
63 EventHandler abortHandler
;
64 AbortHelper abortHelper
;
65 internal WebConnectionData Data
;
67 ChunkStream chunkStream
;
71 HttpWebRequest priority_request
;
72 NetworkCredential ntlm_credentials
;
73 bool ntlm_authenticated
;
82 NtlmAuthState connect_ntlm_auth_state
;
83 HttpWebRequest connect_request
;
85 Exception connect_exception
;
86 MonoTlsStream tlsStream
;
88 #if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
89 [System
.Runtime
.InteropServices
.DllImport ("__Internal")]
90 static extern void xamarin_start_wwan (string uri
);
93 internal ChunkStream ChunkStream
{
94 get { return chunkStream; }
97 public WebConnection (IWebConnectionState wcs
, ServicePoint sPoint
)
100 this.sPoint
= sPoint
;
101 buffer
= new byte [4096];
102 Data
= new WebConnectionData ();
103 initConn
= new WaitCallback (state
=> {
105 InitConnection (state
);
108 queue
= wcs
.Group
.Queue
;
109 abortHelper
= new AbortHelper ();
110 abortHelper
.Connection
= this;
111 abortHandler
= new EventHandler (abortHelper
.Abort
);
115 public WebConnection Connection
;
117 public void Abort (object sender
, EventArgs args
)
119 WebConnection other
= ((HttpWebRequest
) sender
).WebConnection
;
122 other
.Abort (sender
, args
);
128 // The real condition is !(socket.Poll (0, SelectMode.SelectRead) || socket.Available != 0)
129 // but if there's data pending to read (!) we won't reuse the socket.
130 return (socket
.Poll (0, SelectMode
.SelectRead
) == false);
133 void Connect (HttpWebRequest request
)
136 if (socket
!= null && socket
.Connected
&& status
== WebExceptionStatus
.Success
) {
137 // Take the chunked stream to the expected state (State.None)
138 if (CanReuse () && CompleteChunkedRead ()) {
145 if (socket
!= null) {
151 IPHostEntry hostEntry
= sPoint
.HostEntry
;
153 if (hostEntry
== null) {
154 #if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
155 xamarin_start_wwan (sPoint
.Address
.ToString ());
156 hostEntry
= sPoint
.HostEntry
;
157 if (hostEntry
== null) {
159 status
= sPoint
.UsesProxy
? WebExceptionStatus
.ProxyNameResolutionFailure
:
160 WebExceptionStatus
.NameResolutionFailure
;
162 #if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
167 //WebConnectionData data = Data;
168 foreach (IPAddress address
in hostEntry
.AddressList
) {
170 socket
= new Socket (address
.AddressFamily
, SocketType
.Stream
, ProtocolType
.Tcp
);
171 } catch (Exception se
) {
172 // The Socket ctor can throw if we run out of FD's
173 if (!request
.Aborted
)
174 status
= WebExceptionStatus
.ConnectFailure
;
175 connect_exception
= se
;
178 IPEndPoint remote
= new IPEndPoint (address
, sPoint
.Address
.Port
);
179 socket
.NoDelay
= !sPoint
.UseNagleAlgorithm
;
181 sPoint
.KeepAliveSetup (socket
);
183 // Ignore. Not supported in all platforms.
186 if (!sPoint
.CallEndPointDelegate (socket
, remote
)) {
189 status
= WebExceptionStatus
.ConnectFailure
;
194 socket
.Connect (remote
);
195 status
= WebExceptionStatus
.Success
;
197 } catch (ThreadAbortException
) {
198 // program exiting...
204 } catch (ObjectDisposedException
) {
205 // socket closed from another thread
207 } catch (Exception exc
) {
212 if (!request
.Aborted
)
213 status
= WebExceptionStatus
.ConnectFailure
;
214 connect_exception
= exc
;
221 bool CreateTunnel (HttpWebRequest request
, Uri connectUri
,
222 Stream stream
, out byte[] buffer
)
224 StringBuilder sb
= new StringBuilder ();
225 sb
.Append ("CONNECT ");
226 sb
.Append (request
.Address
.Host
);
228 sb
.Append (request
.Address
.Port
);
229 sb
.Append (" HTTP/");
230 if (request
.ServicePoint
.ProtocolVersion
== HttpVersion
.Version11
)
235 sb
.Append ("\r\nHost: ");
236 sb
.Append (request
.Address
.Authority
);
239 var challenge
= Data
.Challenge
;
240 Data
.Challenge
= null;
241 var auth_header
= request
.Headers
["Proxy-Authorization"];
242 bool have_auth
= auth_header
!= null;
244 sb
.Append ("\r\nProxy-Authorization: ");
245 sb
.Append (auth_header
);
246 ntlm
= auth_header
.ToUpper ().Contains ("NTLM");
247 } else if (challenge
!= null && Data
.StatusCode
== 407) {
248 ICredentials creds
= request
.Proxy
.Credentials
;
251 if (connect_request
== null) {
252 // create a CONNECT request to use with Authenticate
253 connect_request
= (HttpWebRequest
)WebRequest
.Create (
254 connectUri
.Scheme
+ "://" + connectUri
.Host
+ ":" + connectUri
.Port
+ "/");
255 connect_request
.Method
= "CONNECT";
256 connect_request
.Credentials
= creds
;
260 for (int i
= 0; i
< challenge
.Length
; i
++) {
261 var auth
= AuthenticationManager
.Authenticate (challenge
[i
], connect_request
, creds
);
264 ntlm
= (auth
.ModuleAuthenticationType
== "NTLM");
265 sb
.Append ("\r\nProxy-Authorization: ");
266 sb
.Append (auth
.Message
);
273 sb
.Append ("\r\nProxy-Connection: keep-alive");
274 connect_ntlm_auth_state
++;
277 sb
.Append ("\r\n\r\n");
280 byte [] connectBytes
= Encoding
.Default
.GetBytes (sb
.ToString ());
281 stream
.Write (connectBytes
, 0, connectBytes
.Length
);
284 WebHeaderCollection result
= ReadHeaders (stream
, out buffer
, out status
);
285 if ((!have_auth
|| connect_ntlm_auth_state
== NtlmAuthState
.Challenge
) &&
286 result
!= null && status
== 407) { // Needs proxy auth
287 var connectionHeader
= result
["Connection"];
288 if (socket
!= null && !string.IsNullOrEmpty (connectionHeader
) &&
289 connectionHeader
.ToLower() == "close") {
290 // The server is requesting that this connection be closed
295 Data
.StatusCode
= status
;
296 Data
.Challenge
= result
.GetValues ("Proxy-Authenticate");
297 Data
.Headers
= result
;
302 Data
.StatusCode
= status
;
303 Data
.Headers
= result
;
307 return (result
!= null);
310 WebHeaderCollection
ReadHeaders (Stream stream
, out byte [] retBuffer
, out int status
)
315 byte [] buffer
= new byte [1024];
316 MemoryStream ms
= new MemoryStream ();
319 int n
= stream
.Read (buffer
, 0, 1024);
321 HandleError (WebExceptionStatus
.ServerProtocolViolation
, null, "ReadHeaders");
325 ms
.Write (buffer
, 0, n
);
328 bool gotStatus
= false;
329 WebHeaderCollection headers
= new WebHeaderCollection ();
330 while (ReadLine (ms
.GetBuffer (), ref start
, (int) ms
.Length
, ref str
)) {
334 contentLen
= int.Parse(headers
["Content-Length"]);
340 if (ms
.Length
- start
- contentLen
> 0) {
341 // we've read more data than the response header and conents,
342 // give back extra data to the caller
343 retBuffer
= new byte[ms
.Length
- start
- contentLen
];
344 Buffer
.BlockCopy(ms
.GetBuffer(), start
+ contentLen
, retBuffer
, 0, retBuffer
.Length
);
347 // haven't read in some or all of the contents for the response, do so now
348 FlushContents(stream
, contentLen
- (int)(ms
.Length
- start
));
359 string[] parts
= str
.Split (' ');
360 if (parts
.Length
< 2) {
361 HandleError (WebExceptionStatus
.ServerProtocolViolation
, null, "ReadHeaders2");
365 if (String
.Compare (parts
[0], "HTTP/1.1", true) == 0)
366 Data
.ProxyVersion
= HttpVersion
.Version11
;
367 else if (String
.Compare (parts
[0], "HTTP/1.0", true) == 0)
368 Data
.ProxyVersion
= HttpVersion
.Version10
;
370 HandleError (WebExceptionStatus
.ServerProtocolViolation
, null, "ReadHeaders2");
374 status
= (int)UInt32
.Parse (parts
[1]);
375 if (parts
.Length
>= 3)
376 Data
.StatusDescription
= String
.Join (" ", parts
, 2, parts
.Length
- 2);
383 void FlushContents(Stream stream
, int contentLength
)
385 while (contentLength
> 0) {
386 byte[] contentBuffer
= new byte[contentLength
];
387 int bytesRead
= stream
.Read(contentBuffer
, 0, contentLength
);
389 contentLength
-= bytesRead
;
397 bool CreateStream (HttpWebRequest request
)
400 NetworkStream serverStream
= new NetworkStream (socket
, false);
402 if (request
.Address
.Scheme
== Uri
.UriSchemeHttps
) {
404 if (!reused
|| nstream
== null || tlsStream
== null) {
405 byte [] buffer
= null;
406 if (sPoint
.UseConnect
) {
407 bool ok
= CreateTunnel (request
, sPoint
.Address
, serverStream
, out buffer
);
411 tlsStream
= new MonoTlsStream (request
, serverStream
);
412 nstream
= tlsStream
.CreateStream (buffer
);
414 // we also need to set ServicePoint.Certificate
415 // and ServicePoint.ClientCertificate but this can
416 // only be done later (after handshake - which is
417 // done only after a read operation).
419 throw new NotSupportedException ();
422 nstream
= serverStream
;
424 } catch (Exception ex
) {
425 if (tlsStream
!= null)
426 status
= tlsStream
.ExceptionStatus
;
427 else if (!request
.Aborted
)
428 status
= WebExceptionStatus
.ConnectFailure
;
429 connect_exception
= ex
;
436 void HandleError (WebExceptionStatus st
, Exception e
, string where
)
440 if (st
== WebExceptionStatus
.RequestCanceled
)
441 Data
= new WebConnectionData ();
444 if (e
== null) { // At least we now where it comes from
446 throw new Exception (new System
.Diagnostics
.StackTrace ().ToString ());
447 } catch (Exception e2
) {
452 HttpWebRequest req
= null;
453 if (Data
!= null && Data
.request
!= null)
458 req
.FinishedReading
= true;
459 req
.SetResponseError (st
, e
, where
);
463 static void ReadDone (IAsyncResult result
)
465 WebConnection cnc
= (WebConnection
)result
.AsyncState
;
466 WebConnectionData data
= cnc
.Data
;
467 Stream ns
= cnc
.nstream
;
475 nread
= ns
.EndRead (result
);
476 } catch (ObjectDisposedException
) {
478 } catch (Exception e
) {
479 if (e
.InnerException
is ObjectDisposedException
)
482 cnc
.HandleError (WebExceptionStatus
.ReceiveFailure
, e
, "ReadDone1");
487 cnc
.HandleError (WebExceptionStatus
.ReceiveFailure
, null, "ReadDone2");
492 cnc
.HandleError (WebExceptionStatus
.ServerProtocolViolation
, null, "ReadDone3");
497 nread
+= cnc
.position
;
498 if (data
.ReadState
== ReadState
.None
) {
499 Exception exc
= null;
501 pos
= GetResponse (data
, cnc
.sPoint
, cnc
.buffer
, nread
);
502 } catch (Exception e
) {
506 if (exc
!= null || pos
== -1) {
507 cnc
.HandleError (WebExceptionStatus
.ServerProtocolViolation
, exc
, "ReadDone4");
512 if (data
.ReadState
== ReadState
.Aborted
) {
513 cnc
.HandleError (WebExceptionStatus
.RequestCanceled
, null, "ReadDone");
517 if (data
.ReadState
!= ReadState
.Content
) {
519 int max
= (est
< cnc
.buffer
.Length
) ? cnc
.buffer
.Length
: est
;
520 byte [] newBuffer
= new byte [max
];
521 Buffer
.BlockCopy (cnc
.buffer
, 0, newBuffer
, 0, nread
);
522 cnc
.buffer
= newBuffer
;
523 cnc
.position
= nread
;
524 data
.ReadState
= ReadState
.None
;
531 WebConnectionStream stream
= new WebConnectionStream (cnc
, data
);
532 bool expect_content
= ExpectContent (data
.StatusCode
, data
.request
.Method
);
533 string tencoding
= null;
535 tencoding
= data
.Headers
["Transfer-Encoding"];
537 cnc
.chunkedRead
= (tencoding
!= null && tencoding
.IndexOf ("chunked", StringComparison
.OrdinalIgnoreCase
) != -1);
538 if (!cnc
.chunkedRead
) {
539 stream
.ReadBuffer
= cnc
.buffer
;
540 stream
.ReadBufferOffset
= pos
;
541 stream
.ReadBufferSize
= nread
;
543 stream
.CheckResponseInBuffer ();
544 } catch (Exception e
) {
545 cnc
.HandleError (WebExceptionStatus
.ReceiveFailure
, e
, "ReadDone7");
547 } else if (cnc
.chunkStream
== null) {
549 cnc
.chunkStream
= new ChunkStream (cnc
.buffer
, pos
, nread
, data
.Headers
);
550 } catch (Exception e
) {
551 cnc
.HandleError (WebExceptionStatus
.ServerProtocolViolation
, e
, "ReadDone5");
555 cnc
.chunkStream
.ResetBuffer ();
557 cnc
.chunkStream
.Write (cnc
.buffer
, pos
, nread
);
558 } catch (Exception e
) {
559 cnc
.HandleError (WebExceptionStatus
.ServerProtocolViolation
, e
, "ReadDone6");
564 data
.stream
= stream
;
567 stream
.ForceCompletion ();
569 data
.request
.SetResponseData (data
);
572 static bool ExpectContent (int statusCode
, string method
)
574 if (method
== "HEAD")
576 return (statusCode
>= 200 && statusCode
!= 204 && statusCode
!= 304);
579 internal static void InitRead (object state
)
581 WebConnection cnc
= (WebConnection
) state
;
582 Stream ns
= cnc
.nstream
;
585 int size
= cnc
.buffer
.Length
- cnc
.position
;
586 ns
.BeginRead (cnc
.buffer
, cnc
.position
, size
, readDoneDelegate
, cnc
);
587 } catch (Exception e
) {
588 cnc
.HandleError (WebExceptionStatus
.ReceiveFailure
, e
, "InitRead");
592 static int GetResponse (WebConnectionData data
, ServicePoint sPoint
,
593 byte [] buffer
, int max
)
598 bool isContinue
= false;
599 bool emptyFirstLine
= false;
601 if (data
.ReadState
== ReadState
.Aborted
)
604 if (data
.ReadState
== ReadState
.None
) {
605 lineok
= ReadLine (buffer
, ref pos
, max
, ref line
);
610 emptyFirstLine
= true;
613 emptyFirstLine
= false;
614 data
.ReadState
= ReadState
.Status
;
616 string [] parts
= line
.Split (' ');
617 if (parts
.Length
< 2)
620 if (String
.Compare (parts
[0], "HTTP/1.1", true) == 0) {
621 data
.Version
= HttpVersion
.Version11
;
622 sPoint
.SetVersion (HttpVersion
.Version11
);
624 data
.Version
= HttpVersion
.Version10
;
625 sPoint
.SetVersion (HttpVersion
.Version10
);
628 data
.StatusCode
= (int) UInt32
.Parse (parts
[1]);
629 if (parts
.Length
>= 3)
630 data
.StatusDescription
= String
.Join (" ", parts
, 2, parts
.Length
- 2);
632 data
.StatusDescription
= "";
638 emptyFirstLine
= false;
639 if (data
.ReadState
== ReadState
.Status
) {
640 data
.ReadState
= ReadState
.Headers
;
641 data
.Headers
= new WebHeaderCollection ();
642 ArrayList headers
= new ArrayList ();
643 bool finished
= false;
645 if (ReadLine (buffer
, ref pos
, max
, ref line
) == false)
649 // Empty line: end of headers
654 if (line
.Length
> 0 && (line
[0] == ' ' || line
[0] == '\t')) {
655 int count
= headers
.Count
- 1;
659 string prev
= (string) headers
[count
] + line
;
660 headers
[count
] = prev
;
669 // .NET uses ParseHeaders or ParseHeadersStrict which is much better
670 foreach (string s
in headers
) {
672 int pos_s
= s
.IndexOf (':');
674 throw new ArgumentException ("no colon found", "header");
676 var header
= s
.Substring (0, pos_s
);
677 var value = s
.Substring (pos_s
+ 1).Trim ();
679 var h
= data
.Headers
;
680 if (WebHeaderCollection
.AllowMultiValues (header
)) {
681 h
.AddInternal (header
, value);
683 h
.SetInternal (header
, value);
687 if (data
.StatusCode
== (int) HttpStatusCode
.Continue
) {
688 sPoint
.SendContinue
= true;
692 if (data
.request
.ExpectContinue
) {
693 data
.request
.DoContinueDelegate (data
.StatusCode
, data
.Headers
);
694 // Prevent double calls when getting the
695 // headers in several packets.
696 data
.request
.ExpectContinue
= false;
699 data
.ReadState
= ReadState
.None
;
703 data
.ReadState
= ReadState
.Content
;
707 } while (emptyFirstLine
|| isContinue
);
712 void InitConnection (object state
)
714 HttpWebRequest request
= (HttpWebRequest
) state
;
715 request
.WebConnection
= this;
716 if (request
.ReuseConnection
)
717 request
.StoredConnection
= this;
722 keepAlive
= request
.KeepAlive
;
723 Data
= new WebConnectionData (request
);
729 if (status
!= WebExceptionStatus
.Success
) {
730 if (!request
.Aborted
) {
731 request
.SetWriteStreamError (status
, connect_exception
);
737 if (!CreateStream (request
)) {
741 WebExceptionStatus st
= status
;
742 if (Data
.Challenge
!= null)
745 Exception cnc_exc
= connect_exception
;
746 if (cnc_exc
== null && (Data
.StatusCode
== 401 || Data
.StatusCode
== 407)) {
747 st
= WebExceptionStatus
.ProtocolError
;
748 if (Data
.Headers
== null)
749 Data
.Headers
= new WebHeaderCollection ();
751 var webResponse
= new HttpWebResponse (sPoint
.Address
, "CONNECT", Data
, null);
752 cnc_exc
= new WebException (Data
.StatusCode
== 407 ? "(407) Proxy Authentication Required" : "(401) Unauthorized", null, st
, webResponse
);
755 connect_exception
= null;
756 request
.SetWriteStreamError (st
, cnc_exc
);
761 request
.SetWriteStream (new WebConnectionStream (this, request
));
765 static bool warned_about_queue
= false;
768 internal EventHandler
SendRequest (HttpWebRequest request
)
774 if (state
.TrySetBusy ()) {
775 status
= WebExceptionStatus
.Success
;
776 ThreadPool
.QueueUserWorkItem (initConn
, request
);
780 if (!warned_about_queue
) {
781 warned_about_queue
= true;
782 Console
.WriteLine ("WARNING: An HttpWebRequest was added to the ConnectionGroup queue because the connection limit was reached.");
785 queue
.Enqueue (request
);
796 if (queue
.Count
> 0) {
797 SendRequest ((HttpWebRequest
) queue
.Dequeue ());
802 internal void NextRead ()
805 if (Data
.request
!= null)
806 Data
.request
.FinishedReading
= true;
807 string header
= (sPoint
.UsesProxy
) ? "Proxy-Connection" : "Connection";
808 string cncHeader
= (Data
.Headers
!= null) ? Data
.Headers
[header
] : null;
809 bool keepAlive
= (Data
.Version
== HttpVersion
.Version11
&& this.keepAlive
);
810 if (Data
.ProxyVersion
!= null && Data
.ProxyVersion
!= HttpVersion
.Version11
)
812 if (cncHeader
!= null) {
813 cncHeader
= cncHeader
.ToLower ();
814 keepAlive
= (this.keepAlive
&& cncHeader
.IndexOf ("keep-alive", StringComparison
.Ordinal
) != -1);
817 if ((socket
!= null && !socket
.Connected
) ||
818 (!keepAlive
|| (cncHeader
!= null && cncHeader
.IndexOf ("close", StringComparison
.Ordinal
) != -1))) {
823 if (priority_request
!= null) {
824 SendRequest (priority_request
);
825 priority_request
= null;
832 static bool ReadLine (byte [] buffer
, ref int start
, int max
, ref string output
)
834 bool foundCR
= false;
835 StringBuilder text
= new StringBuilder ();
838 while (start
< max
) {
839 c
= (int) buffer
[start
++];
841 if (c
== '\n') { // newline
842 if ((text
.Length
> 0) && (text
[text
.Length
- 1] == '\r'))
847 } else if (foundCR
) {
856 text
.Append ((char) c
);
859 if (c
!= '\n' && c
!= '\r')
862 if (text
.Length
== 0) {
864 return (c
== '\n' || c
== '\r');
870 output
= text
.ToString ();
875 internal IAsyncResult
BeginRead (HttpWebRequest request
, byte [] buffer
, int offset
, int size
, AsyncCallback cb
, object state
)
879 if (Data
.request
!= request
)
880 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
886 IAsyncResult result
= null;
887 if (!chunkedRead
|| (!chunkStream
.DataAvailable
&& chunkStream
.WantMore
)) {
889 result
= s
.BeginRead (buffer
, offset
, size
, cb
, state
);
891 } catch (Exception
) {
892 HandleError (WebExceptionStatus
.ReceiveFailure
, null, "chunked BeginRead");
898 WebAsyncResult wr
= new WebAsyncResult (cb
, state
, buffer
, offset
, size
);
899 wr
.InnerAsyncResult
= result
;
900 if (result
== null) {
901 // Will be completed from the data in ChunkStream
902 wr
.SetCompleted (true, (Exception
) null);
911 internal int EndRead (HttpWebRequest request
, IAsyncResult result
)
916 throw new WebException ("Request aborted", WebExceptionStatus
.RequestCanceled
);
917 if (Data
.request
!= request
)
918 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
920 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
926 WebAsyncResult wr
= null;
927 IAsyncResult nsAsync
= ((WebAsyncResult
) result
).InnerAsyncResult
;
928 if (chunkedRead
&& (nsAsync
is WebAsyncResult
)) {
929 wr
= (WebAsyncResult
) nsAsync
;
930 IAsyncResult inner
= wr
.InnerAsyncResult
;
931 if (inner
!= null && !(inner
is WebAsyncResult
)) {
932 nbytes
= s
.EndRead (inner
);
935 } else if (!(nsAsync
is WebAsyncResult
)) {
936 nbytes
= s
.EndRead (nsAsync
);
937 wr
= (WebAsyncResult
) result
;
943 chunkStream
.WriteAndReadBack (wr
.Buffer
, wr
.Offset
, wr
.Size
, ref nbytes
);
944 if (!done
&& nbytes
== 0 && chunkStream
.WantMore
)
945 nbytes
= EnsureRead (wr
.Buffer
, wr
.Offset
, wr
.Size
);
946 } catch (Exception e
) {
947 if (e
is WebException
)
950 throw new WebException ("Invalid chunked data.", e
,
951 WebExceptionStatus
.ServerProtocolViolation
, null);
954 if ((done
|| nbytes
== 0) && chunkStream
.ChunkLeft
!= 0) {
955 HandleError (WebExceptionStatus
.ReceiveFailure
, null, "chunked EndRead");
956 throw new WebException ("Read error", null, WebExceptionStatus
.ReceiveFailure
, null);
960 return (nbytes
!= 0) ? nbytes
: -1;
963 // To be called on chunkedRead when we can read no data from the ChunkStream yet
964 int EnsureRead (byte [] buffer
, int offset
, int size
)
966 byte [] morebytes
= null;
968 while (nbytes
== 0 && chunkStream
.WantMore
) {
969 int localsize
= chunkStream
.ChunkLeft
;
970 if (localsize
<= 0) // not read chunk size yet
972 else if (localsize
> 16384)
975 if (morebytes
== null || morebytes
.Length
< localsize
)
976 morebytes
= new byte [localsize
];
978 int nread
= nstream
.Read (morebytes
, 0, localsize
);
982 chunkStream
.Write (morebytes
, 0, nread
);
983 nbytes
+= chunkStream
.Read (buffer
, offset
+ nbytes
, size
- nbytes
);
989 bool CompleteChunkedRead()
991 if (!chunkedRead
|| chunkStream
== null)
994 while (chunkStream
.WantMore
) {
995 int nbytes
= nstream
.Read (buffer
, 0, buffer
.Length
);
997 return false; // Socket was disconnected
999 chunkStream
.Write (buffer
, 0, nbytes
);
1005 internal IAsyncResult
BeginWrite (HttpWebRequest request
, byte [] buffer
, int offset
, int size
, AsyncCallback cb
, object state
)
1009 if (Data
.request
!= request
)
1010 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
1011 if (nstream
== null)
1016 IAsyncResult result
= null;
1018 result
= s
.BeginWrite (buffer
, offset
, size
, cb
, state
);
1019 } catch (ObjectDisposedException
) {
1021 if (Data
.request
!= request
)
1025 } catch (IOException e
) {
1026 SocketException se
= e
.InnerException
as SocketException
;
1027 if (se
!= null && se
.SocketErrorCode
== SocketError
.NotConnected
) {
1031 } catch (Exception
) {
1032 status
= WebExceptionStatus
.SendFailure
;
1039 internal bool EndWrite (HttpWebRequest request
, bool throwOnError
, IAsyncResult result
)
1043 if (status
== WebExceptionStatus
.RequestCanceled
)
1045 if (Data
.request
!= request
)
1046 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
1047 if (nstream
== null)
1048 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
1053 s
.EndWrite (result
);
1055 } catch (Exception exc
) {
1056 status
= WebExceptionStatus
.SendFailure
;
1057 if (throwOnError
&& exc
.InnerException
!= null)
1058 throw exc
.InnerException
;
1063 internal int Read (HttpWebRequest request
, byte [] buffer
, int offset
, int size
)
1067 if (Data
.request
!= request
)
1068 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
1069 if (nstream
== null)
1078 result
= s
.Read (buffer
, offset
, size
);
1079 done
= (result
== 0);
1084 chunkStream
.WriteAndReadBack (buffer
, offset
, size
, ref result
);
1085 if (!done
&& result
== 0 && chunkStream
.WantMore
)
1086 result
= EnsureRead (buffer
, offset
, size
);
1087 } catch (Exception e
) {
1088 HandleError (WebExceptionStatus
.ReceiveFailure
, e
, "chunked Read1");
1092 if ((done
|| result
== 0) && chunkStream
.WantMore
) {
1093 HandleError (WebExceptionStatus
.ReceiveFailure
, null, "chunked Read2");
1094 throw new WebException ("Read error", null, WebExceptionStatus
.ReceiveFailure
, null);
1097 } catch (Exception e
) {
1098 HandleError (WebExceptionStatus
.ReceiveFailure
, e
, "Read");
1104 internal bool Write (HttpWebRequest request
, byte [] buffer
, int offset
, int size
, ref string err_msg
)
1109 if (Data
.request
!= request
)
1110 throw new ObjectDisposedException (typeof (NetworkStream
).FullName
);
1117 s
.Write (buffer
, offset
, size
);
1118 } catch (Exception e
) {
1119 err_msg
= e
.Message
;
1120 WebExceptionStatus wes
= WebExceptionStatus
.SendFailure
;
1121 string msg
= "Write: " + err_msg
;
1122 if (e
is WebException
) {
1123 HandleError (wes
, e
, msg
);
1127 HandleError (wes
, e
, msg
);
1133 internal void Close (bool sendNext
)
1136 if (Data
!= null && Data
.request
!= null && Data
.request
.ReuseConnection
) {
1137 Data
.request
.ReuseConnection
= false;
1141 if (nstream
!= null) {
1148 if (socket
!= null) {
1155 if (ntlm_authenticated
)
1159 Data
.ReadState
= ReadState
.Aborted
;
1163 Data
= new WebConnectionData ();
1167 connect_request
= null;
1168 connect_ntlm_auth_state
= NtlmAuthState
.None
;
1172 void Abort (object sender
, EventArgs args
)
1176 HttpWebRequest req
= (HttpWebRequest
) sender
;
1177 if (Data
.request
== req
|| Data
.request
== null) {
1178 if (!req
.FinishedReading
) {
1179 status
= WebExceptionStatus
.RequestCanceled
;
1181 if (queue
.Count
> 0) {
1182 Data
.request
= (HttpWebRequest
) queue
.Dequeue ();
1183 SendRequest (Data
.request
);
1189 req
.FinishedReading
= true;
1190 req
.SetResponseError (WebExceptionStatus
.RequestCanceled
, null, "User aborted");
1191 if (queue
.Count
> 0 && queue
.Peek () == sender
) {
1193 } else if (queue
.Count
> 0) {
1194 object [] old
= queue
.ToArray ();
1196 for (int i
= old
.Length
- 1; i
>= 0; i
--) {
1197 if (old
[i
] != sender
)
1198 queue
.Enqueue (old
[i
]);
1205 internal void ResetNtlm ()
1207 ntlm_authenticated
= false;
1208 ntlm_credentials
= null;
1209 unsafe_sharing
= false;
1212 internal bool Connected
{
1215 return (socket
!= null && socket
.Connected
);
1220 // -Used for NTLM authentication
1221 internal HttpWebRequest PriorityRequest
{
1222 set { priority_request = value; }
1225 internal bool NtlmAuthenticated
{
1226 get { return ntlm_authenticated; }
1227 set { ntlm_authenticated = value; }
1230 internal NetworkCredential NtlmCredential
{
1231 get { return ntlm_credentials; }
1232 set { ntlm_credentials = value; }
1235 internal bool UnsafeAuthenticatedConnectionSharing
{
1236 get { return unsafe_sharing; }
1237 set { unsafe_sharing = value; }