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 piClient;
56 static PropertyInfo piServer;
58 public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
62 buffer = new byte [4096];
63 readState = ReadState.None;
64 Data = new WebConnectionData ();
65 initConn = new WaitOrTimerCallback (InitConnection);
66 abortHandler = new EventHandler (Abort);
67 goAhead = new AutoResetEvent (true);
71 public void Connect ()
74 if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
86 IPHostEntry hostEntry = sPoint.HostEntry;
88 if (hostEntry == null) {
89 status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
90 WebExceptionStatus.NameResolutionFailure;
94 foreach (IPAddress address in hostEntry.AddressList) {
95 socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
97 socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
98 status = WebExceptionStatus.Success;
100 } catch (SocketException) {
103 status = WebExceptionStatus.ConnectFailure;
109 bool CreateStream (HttpWebRequest request)
112 NetworkStream serverStream = new NetworkStream (socket, false);
113 if (request.RequestUri.Scheme == Uri.UriSchemeHttps) {
116 lock (typeof (WebConnection)) {
118 // HttpsClientStream is an internal glue class in Mono.Security.dll
119 sslStream = Type.GetType ("Mono.Security.Protocol.Tls.HttpsClientStream, " + Consts.AssemblyMono_Security, false);
120 if (sslStream != null) {
121 piClient = sslStream.GetProperty ("SelectedClientCertificate");
122 piServer = sslStream.GetProperty ("ServerCertificate");
126 if (sslStream == null)
127 throw new NotSupportedException ("Missing Mono.Security.dll assembly. Support for SSL/TLS is unavailable.");
129 object[] args = new object [4] { serverStream, request.RequestUri.Host, request.ClientCertificates, request };
130 nstream = (Stream) Activator.CreateInstance (sslStream, args);
132 // we also need to set ServicePoint.Certificate
133 // and ServicePoint.ClientCertificate but this can
134 // only be done later (after handshake - which is
135 // done only after a read operation).
138 nstream = serverStream;
139 } catch (Exception) {
140 status = WebExceptionStatus.ConnectFailure;
147 void HandleError (WebExceptionStatus st, Exception e)
152 if (st == WebExceptionStatus.RequestCanceled)
153 Data = new WebConnectionData ();
158 if (e == null) { // At least we now where it comes from
160 throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
161 } catch (Exception e2) {
166 if (Data != null && Data.request != null)
167 Data.request.SetResponseError (st, e);
172 static void ReadDone (IAsyncResult result)
174 WebConnection cnc = (WebConnection) result.AsyncState;
175 WebConnectionData data = cnc.Data;
176 Stream ns = cnc.nstream;
184 nread = ns.EndRead (result);
185 } catch (Exception e) {
186 cnc.status = WebExceptionStatus.ReceiveFailure;
187 cnc.HandleError (cnc.status, e);
192 cnc.status = WebExceptionStatus.ReceiveFailure;
193 cnc.HandleError (cnc.status, null);
198 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
202 //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread + cnc.position));
204 nread += cnc.position;
205 if (cnc.readState == ReadState.None) {
206 Exception exc = null;
208 pos = cnc.GetResponse (cnc.buffer, nread);
209 } catch (Exception e) {
214 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
219 if (cnc.readState != ReadState.Content) {
221 int max = (est < cnc.buffer.Length) ? cnc.buffer.Length : est;
222 byte [] newBuffer = new byte [max];
223 Buffer.BlockCopy (cnc.buffer, 0, newBuffer, 0, nread);
224 cnc.buffer = newBuffer;
225 cnc.position = nread;
226 cnc.readState = ReadState.None;
233 WebConnectionStream stream = new WebConnectionStream (cnc);
235 string contentType = data.Headers ["Transfer-Encoding"];
236 cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
237 if (!cnc.chunkedRead) {
238 stream.ReadBuffer = cnc.buffer;
239 stream.ReadBufferOffset = pos;
240 stream.ReadBufferSize = nread;
241 } else if (cnc.chunkStream == null) {
242 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
244 cnc.chunkStream.ResetBuffer ();
245 cnc.chunkStream.Write (cnc.buffer, pos, nread);
248 data.stream = stream;
252 if (cnc.queue.Count > 0) {
255 cnc.prevStream = stream;
256 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.SetInternal (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,
428 queue.Enqueue (request);
439 if (queue.Count > 0) {
441 SendRequest ((HttpWebRequest) queue.Dequeue ());
446 internal void NextRead ()
450 string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
451 string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
452 bool keepAlive = (Data.Version == HttpVersion.Version11);
453 if (cncHeader != null) {
454 cncHeader = cncHeader.ToLower ();
455 keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
458 if ((socket != null && !socket.Connected) ||
459 (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
465 if (queue.Count > 0) {
467 SendRequest ((HttpWebRequest) queue.Dequeue ());
473 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
475 bool foundCR = false;
476 StringBuilder text = new StringBuilder ();
479 while (start < max) {
480 c = (int) buffer [start++];
482 if (c == '\n') { // newline
483 if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
488 } else if (foundCR) {
497 text.Append ((char) c);
500 if (c != '\n' && c != '\r')
503 if (text.Length == 0) {
505 return (c == '\n' || c == '\r');
511 output = text.ToString ();
515 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
520 IAsyncResult result = null;
521 if (!chunkedRead || chunkStream.WantMore) {
523 result = nstream.BeginRead (buffer, offset, size, cb, state);
524 } catch (Exception) {
525 status = WebExceptionStatus.ReceiveFailure;
531 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
532 wr.InnerAsyncResult = result;
539 internal int EndRead (IAsyncResult result)
545 WebAsyncResult wr = (WebAsyncResult) result;
547 if (wr.InnerAsyncResult != null)
548 nbytes = nstream.EndRead (wr.InnerAsyncResult);
550 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
551 if (nbytes == 0 && chunkStream.WantMore) {
552 nbytes = nstream.Read (wr.Buffer, wr.Offset, wr.Size);
553 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
558 return nstream.EndRead (result);
561 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
563 IAsyncResult result = null;
568 result = nstream.BeginWrite (buffer, offset, size, cb, state);
569 } catch (Exception) {
570 status = WebExceptionStatus.SendFailure;
577 internal void EndWrite (IAsyncResult result)
580 nstream.EndWrite (result);
583 internal int Read (byte [] buffer, int offset, int size)
590 if (!chunkedRead || chunkStream.WantMore)
591 result = nstream.Read (buffer, offset, size);
594 chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
595 } catch (Exception e) {
596 status = WebExceptionStatus.ReceiveFailure;
597 HandleError (status, e);
603 internal void Write (byte [] buffer, int offset, int size)
609 nstream.Write (buffer, offset, size);
610 // here SSL handshake should have been done
611 if (ssl && !certsAvailable) {
614 } catch (Exception) {
618 internal bool TryReconnect ()
622 HandleError (WebExceptionStatus.SendFailure, null);
629 if (status != WebExceptionStatus.Success) {
630 HandleError (WebExceptionStatus.SendFailure, null);
634 if (!CreateStream (Data.request)) {
635 HandleError (WebExceptionStatus.SendFailure, null);
642 void Close (bool sendNext)
646 if (nstream != null) {
653 if (socket != null) {
667 void Abort (object sender, EventArgs args)
669 HandleError (WebExceptionStatus.RequestCanceled, null);
673 get { lock (this) return busy; }
676 internal bool Connected {
679 return (socket != null && socket.Connected);