2 // System.Net.WebConnection
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
11 using System.Collections;
12 using System.Net.Sockets;
13 using System.Reflection;
14 using System.Security.Cryptography.X509Certificates;
16 using System.Threading;
33 WebExceptionStatus status;
34 WebConnectionGroup group;
36 WaitOrTimerCallback initConn;
39 static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
40 EventHandler abortHandler;
42 internal WebConnectionData Data;
43 WebConnectionStream prevStream;
45 ChunkStream chunkStream;
46 AutoResetEvent goAhead;
54 static Type sslStream;
55 static PropertyInfo piCRL;
56 static PropertyInfo piClient;
57 static PropertyInfo piServer;
59 public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
63 buffer = new byte [4096];
64 readState = ReadState.None;
65 Data = new WebConnectionData ();
66 initConn = new WaitOrTimerCallback (InitConnection);
67 abortHandler = new EventHandler (Abort);
68 goAhead = new AutoResetEvent (true);
69 queue = new Queue (1);
72 public void Connect ()
75 if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
87 IPHostEntry hostEntry = sPoint.HostEntry;
89 if (hostEntry == null) {
90 status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
91 WebExceptionStatus.NameResolutionFailure;
95 foreach (IPAddress address in hostEntry.AddressList) {
96 socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
98 socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
99 status = WebExceptionStatus.Success;
101 } catch (SocketException) {
104 status = WebExceptionStatus.ConnectFailure;
110 bool CreateStream (HttpWebRequest request)
113 NetworkStream serverStream = new NetworkStream (socket, false);
114 if (request.RequestUri.Scheme == Uri.UriSchemeHttps) {
117 lock (typeof (WebConnection)) {
119 // HttpsClientStream is an internal glue class in Mono.Security.dll
120 sslStream = Type.GetType ("Mono.Security.Protocol.Tls.HttpsClientStream, Mono.Security, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
121 if (sslStream != null) {
122 piClient = sslStream.GetProperty ("SelectedClientCertificate");
123 piServer = sslStream.GetProperty ("ServerCertificate");
127 if (sslStream == null)
128 throw new NotSupportedException ("Missing Mono.Security.dll assembly. Support for SSL/TLS is unavailable.");
130 object[] args = new object [4] { serverStream, request.RequestUri.Host, request.ClientCertificates, request };
131 nstream = (Stream) Activator.CreateInstance (sslStream, args);
133 // we also need to set ServicePoint.Certificate
134 // and ServicePoint.ClientCertificate but this can
135 // only be done later (after handshake - which is
136 // done only after a read operation).
139 nstream = serverStream;
140 } catch (Exception) {
141 status = WebExceptionStatus.ConnectFailure;
148 void HandleError (WebExceptionStatus st, Exception e)
153 if (st == WebExceptionStatus.RequestCanceled)
154 Data = new WebConnectionData ();
159 if (e == null) { // At least we now where it comes from
161 throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
162 } catch (Exception e2) {
167 if (Data != null && Data.request != null)
168 Data.request.SetResponseError (st, e);
173 static void ReadDone (IAsyncResult result)
175 WebConnection cnc = (WebConnection) result.AsyncState;
176 WebConnectionData data = cnc.Data;
177 Stream ns = cnc.nstream;
185 nread = ns.EndRead (result);
186 } catch (Exception e) {
187 cnc.status = WebExceptionStatus.ReceiveFailure;
188 cnc.HandleError (cnc.status, e);
193 cnc.status = WebExceptionStatus.ReceiveFailure;
194 cnc.HandleError (cnc.status, null);
199 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
203 //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread + cnc.position));
205 nread += cnc.position;
206 if (cnc.readState == ReadState.None) {
207 Exception exc = null;
209 pos = cnc.GetResponse (cnc.buffer, nread);
210 } catch (Exception e) {
215 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
220 if (cnc.readState != ReadState.Content) {
222 int max = (est < cnc.buffer.Length) ? cnc.buffer.Length : est;
223 byte [] newBuffer = new byte [max];
224 Buffer.BlockCopy (cnc.buffer, 0, newBuffer, 0, nread);
225 cnc.buffer = newBuffer;
226 cnc.position = nread;
227 cnc.readState = ReadState.None;
234 WebConnectionStream stream = new WebConnectionStream (cnc);
236 string contentType = data.Headers ["Transfer-Encoding"];
237 cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
238 if (!cnc.chunkedRead) {
239 stream.ReadBuffer = cnc.buffer;
240 stream.ReadBufferOffset = pos;
241 stream.ReadBufferSize = nread;
242 } else if (cnc.chunkStream == null) {
243 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
245 cnc.chunkStream.ResetBuffer ();
246 cnc.chunkStream.Write (cnc.buffer, pos, nread);
249 data.stream = stream;
252 if (cnc.queue.Count > 0)
256 cnc.prevStream = stream;
257 stream.CheckComplete ();
261 data.request.SetResponseData (data);
264 internal void GetCertificates ()
266 // here the SSL negotiation have been done
267 X509Certificate client = (X509Certificate) piClient.GetValue (nstream, null);
268 X509Certificate server = (X509Certificate) piServer.GetValue (nstream, null);
269 sPoint.SetCertificates (client, server);
270 certsAvailable = (server != null);
273 static void InitRead (object state)
275 WebConnection cnc = (WebConnection) state;
276 Stream ns = cnc.nstream;
279 int size = cnc.buffer.Length - cnc.position;
280 ns.BeginRead (cnc.buffer, cnc.position, size, readDoneDelegate, cnc);
281 } catch (Exception e) {
282 cnc.HandleError (WebExceptionStatus.ReceiveFailure, e);
286 int GetResponse (byte [] buffer, int max)
291 bool isContinue = false;
293 if (readState == ReadState.None) {
294 lineok = ReadLine (buffer, ref pos, max, ref line);
298 readState = ReadState.Status;
300 string [] parts = line.Split (' ');
301 if (parts.Length < 2)
304 if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
305 Data.Version = HttpVersion.Version11;
306 sPoint.SetVersion (HttpVersion.Version11);
308 Data.Version = HttpVersion.Version10;
309 sPoint.SetVersion (HttpVersion.Version10);
312 Data.StatusCode = (int) UInt32.Parse (parts [1]);
313 if (parts.Length >= 3)
314 Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
316 Data.StatusDescription = "";
322 if (readState == ReadState.Status) {
323 readState = ReadState.Headers;
324 Data.Headers = new WebHeaderCollection ();
325 ArrayList headers = new ArrayList ();
326 bool finished = false;
328 if (ReadLine (buffer, ref pos, max, ref line) == false)
332 // Empty line: end of headers
337 if (line.Length > 0 && (line [0] == ' ' || line [0] == '\t')) {
338 int count = headers.Count - 1;
342 string prev = (string) headers [count] + line;
343 headers [count] = prev;
352 foreach (string s in headers)
353 Data.Headers.Add (s);
355 if (Data.StatusCode == (int) HttpStatusCode.Continue) {
356 sPoint.SendContinue = true;
360 if (Data.request.ExpectContinue) {
361 Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
362 // Prevent double calls when getting the
363 // headers in several packets.
364 Data.request.ExpectContinue = false;
367 readState = ReadState.None;
371 readState = ReadState.Content;
375 } while (isContinue == true);
380 void InitConnection (object state, bool notUsed)
382 HttpWebRequest request = (HttpWebRequest) state;
384 if (status == WebExceptionStatus.RequestCanceled) {
386 Data = new WebConnectionData ();
392 keepAlive = request.KeepAlive;
393 Data = new WebConnectionData ();
394 Data.request = request;
397 if (status != WebExceptionStatus.Success) {
398 request.SetWriteStreamError (status);
403 if (!CreateStream (request)) {
404 request.SetWriteStreamError (status);
409 readState = ReadState.None;
410 request.SetWriteStream (new WebConnectionStream (this, request));
414 internal EventHandler SendRequest (HttpWebRequest request)
417 if (prevStream != null && socket != null && socket.Connected) {
418 prevStream.ReadAll ();
424 ThreadPool.RegisterWaitForSingleObject (goAhead, initConn,
427 queue.Enqueue (request);
437 if (queue.Count > 0) {
439 SendRequest ((HttpWebRequest) queue.Dequeue ());
444 internal void NextRead ()
448 string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
449 string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
450 bool keepAlive = (Data.Version == HttpVersion.Version11);
451 if (cncHeader != null) {
452 cncHeader = cncHeader.ToLower ();
453 keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
456 if ((socket != null && !socket.Connected) ||
457 (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
462 if (queue.Count > 0) {
464 SendRequest ((HttpWebRequest) queue.Dequeue ());
469 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
471 bool foundCR = false;
472 StringBuilder text = new StringBuilder ();
475 while (start < max) {
476 c = (int) buffer [start++];
478 if (c == '\n') { // newline
479 if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
484 } else if (foundCR) {
493 text.Append ((char) c);
496 if (c != '\n' && c != '\r')
499 if (text.Length == 0) {
501 return (c == '\n' || c == '\r');
507 output = text.ToString ();
511 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
516 IAsyncResult result = null;
517 if (!chunkedRead || chunkStream.WantMore) {
519 result = nstream.BeginRead (buffer, offset, size, cb, state);
520 } catch (Exception) {
521 status = WebExceptionStatus.ReceiveFailure;
527 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
528 wr.InnerAsyncResult = result;
535 internal int EndRead (IAsyncResult result)
541 WebAsyncResult wr = (WebAsyncResult) result;
543 if (wr.InnerAsyncResult != null)
544 nbytes = nstream.EndRead (wr.InnerAsyncResult);
546 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
547 if (nbytes == 0 && chunkStream.WantMore) {
548 nbytes = nstream.Read (wr.Buffer, wr.Offset, wr.Size);
549 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
554 return nstream.EndRead (result);
557 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
559 IAsyncResult result = null;
564 result = nstream.BeginWrite (buffer, offset, size, cb, state);
565 } catch (Exception) {
566 status = WebExceptionStatus.SendFailure;
573 internal void EndWrite (IAsyncResult result)
576 nstream.EndWrite (result);
579 internal int Read (byte [] buffer, int offset, int size)
586 if (!chunkedRead || chunkStream.WantMore)
587 result = nstream.Read (buffer, offset, size);
590 chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
591 } catch (Exception e) {
592 status = WebExceptionStatus.ReceiveFailure;
593 HandleError (status, e);
599 internal void Write (byte [] buffer, int offset, int size)
605 nstream.Write (buffer, offset, size);
606 // here SSL handshake should have been done
607 if (ssl && !certsAvailable) {
610 } catch (Exception) {
614 internal bool TryReconnect ()
618 HandleError (WebExceptionStatus.SendFailure, null);
625 if (status != WebExceptionStatus.Success) {
626 HandleError (WebExceptionStatus.SendFailure, null);
630 if (!CreateStream (Data.request)) {
631 HandleError (WebExceptionStatus.SendFailure, null);
638 void Close (bool sendNext)
642 if (nstream != null) {
649 if (socket != null) {
663 void Abort (object sender, EventArgs args)
665 HandleError (WebExceptionStatus.RequestCanceled, null);
669 get { lock (this) return busy; }
672 internal bool Connected {
675 return (socket != null && socket.Connected);