WebConnection.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. //
  2. // System.Net.WebConnection
  3. //
  4. // Authors:
  5. // Gonzalo Paniagua Javier ([email protected])
  6. //
  7. // (C) 2003 Ximian, Inc (http://www.ximian.com)
  8. //
  9. using System.IO;
  10. using System.Collections;
  11. using System.Net.Sockets;
  12. using System.Reflection;
  13. using System.Security.Cryptography.X509Certificates;
  14. using System.Text;
  15. using System.Threading;
  16. namespace System.Net
  17. {
  18. enum ReadState
  19. {
  20. None,
  21. Status,
  22. Headers,
  23. Content
  24. }
  25. class WebConnection
  26. {
  27. ServicePoint sPoint;
  28. Stream nstream;
  29. Socket socket;
  30. WebExceptionStatus status;
  31. WebConnectionGroup group;
  32. bool busy;
  33. WaitOrTimerCallback initConn;
  34. bool keepAlive;
  35. byte [] buffer;
  36. static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
  37. EventHandler abortHandler;
  38. ReadState readState;
  39. internal WebConnectionData Data;
  40. WebConnectionStream prevStream;
  41. bool chunkedRead;
  42. ChunkStream chunkStream;
  43. AutoResetEvent waitForContinue;
  44. AutoResetEvent goAhead;
  45. bool waitingForContinue;
  46. Queue queue;
  47. bool reused;
  48. bool ssl;
  49. bool certsAvailable;
  50. static bool sslCheck;
  51. static Type sslStream;
  52. static PropertyInfo piCRL;
  53. static PropertyInfo piClient;
  54. static PropertyInfo piServer;
  55. public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
  56. {
  57. this.group = group;
  58. this.sPoint = sPoint;
  59. buffer = new byte [4096];
  60. readState = ReadState.None;
  61. Data = new WebConnectionData ();
  62. initConn = new WaitOrTimerCallback (InitConnection);
  63. abortHandler = new EventHandler (Abort);
  64. goAhead = new AutoResetEvent (true);
  65. queue = new Queue (1);
  66. }
  67. public void Connect ()
  68. {
  69. lock (this) {
  70. if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
  71. reused = true;
  72. return;
  73. }
  74. reused = false;
  75. if (socket != null) {
  76. socket.Close();
  77. socket = null;
  78. }
  79. chunkStream = null;
  80. IPHostEntry hostEntry = sPoint.HostEntry;
  81. if (hostEntry == null) {
  82. status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
  83. WebExceptionStatus.NameResolutionFailure;
  84. return;
  85. }
  86. foreach (IPAddress address in hostEntry.AddressList) {
  87. socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  88. try {
  89. socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
  90. status = WebExceptionStatus.Success;
  91. break;
  92. } catch (SocketException) {
  93. socket.Close();
  94. socket = null;
  95. status = WebExceptionStatus.ConnectFailure;
  96. }
  97. }
  98. }
  99. }
  100. bool CreateStream (HttpWebRequest request)
  101. {
  102. try {
  103. NetworkStream serverStream = new NetworkStream (socket, false);
  104. if (request.RequestUri.Scheme == Uri.UriSchemeHttps) {
  105. ssl = true;
  106. if (!sslCheck) {
  107. lock (typeof (WebConnection)) {
  108. sslCheck = true;
  109. sslStream = Type.GetType ("Mono.Security.Protocol.Tls.SslClientStream, Mono.Security, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", false);
  110. if (sslStream != null) {
  111. piCRL = sslStream.GetProperty ("CheckCertRevocationStatus");
  112. piClient = sslStream.GetProperty ("SelectedClientCertificate");
  113. piServer = sslStream.GetProperty ("ServerCertificate");
  114. }
  115. }
  116. }
  117. if (sslStream == null)
  118. throw new NotSupportedException ("Missing Mono.Security.dll assembly. Support for SSL/TLS is unavailable.");
  119. // FIXME: SecurityProtocolType is defined in System (1.1+) and Mono.Security (for 1.0)
  120. // this results in MissingMethodException :(
  121. // object[] args = new object [5] { serverStream, request.RequestUri.Host, false, ServicePointManager.SecurityProtocol, request.ClientCertificates };
  122. // so we ends up using Default and not ServicePointManager.SecurityProtocol
  123. object[] args = new object [3] { serverStream, request.RequestUri.Host, request.ClientCertificates };
  124. nstream = (Stream) Activator.CreateInstance (sslStream, args);
  125. if (ServicePointManager.CheckCertificateRevocationList) {
  126. // note: default is false
  127. piCRL.SetValue (nstream, true, null);
  128. }
  129. // we also need to set ServicePoint.Certificate
  130. // and ServicePoint.ClientCertificate but this can
  131. // only be done later (after handshake - which is
  132. // done only after a read operation).
  133. }
  134. else
  135. nstream = serverStream;
  136. } catch (Exception) {
  137. status = WebExceptionStatus.ConnectFailure;
  138. return false;
  139. }
  140. return true;
  141. }
  142. void HandleError (WebExceptionStatus st, Exception e)
  143. {
  144. status = st;
  145. lock (this) {
  146. busy = false;
  147. if (st == WebExceptionStatus.RequestCanceled)
  148. Data = new WebConnectionData ();
  149. status = st;
  150. }
  151. if (e == null) { // At least we now where it comes from
  152. try {
  153. throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
  154. } catch (Exception e2) {
  155. e = e2;
  156. }
  157. }
  158. if (Data != null && Data.request != null)
  159. Data.request.SetResponseError (st, e);
  160. Close (true);
  161. }
  162. internal bool WaitForContinue (byte [] headers, int offset, int size)
  163. {
  164. waitingForContinue = sPoint.SendContinue;
  165. if (waitingForContinue && waitForContinue == null)
  166. waitForContinue = new AutoResetEvent (false);
  167. Write (headers, offset, size);
  168. if (!waitingForContinue)
  169. return false;
  170. bool result = waitForContinue.WaitOne (2000, false);
  171. waitingForContinue = false;
  172. if (result) {
  173. sPoint.SendContinue = true;
  174. if (Data.request.ExpectContinue)
  175. Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
  176. } else {
  177. sPoint.SendContinue = false;
  178. }
  179. return result;
  180. }
  181. static void ReadDone (IAsyncResult result)
  182. {
  183. WebConnection cnc = (WebConnection) result.AsyncState;
  184. WebConnectionData data = cnc.Data;
  185. Stream ns = cnc.nstream;
  186. if (ns == null) {
  187. cnc.Close (true);
  188. return;
  189. }
  190. int nread = -1;
  191. try {
  192. nread = ns.EndRead (result);
  193. } catch (Exception e) {
  194. cnc.status = WebExceptionStatus.ReceiveFailure;
  195. cnc.HandleError (cnc.status, e);
  196. return;
  197. }
  198. if (nread == 0) {
  199. cnc.status = WebExceptionStatus.ReceiveFailure;
  200. cnc.HandleError (cnc.status, null);
  201. return;
  202. }
  203. if (nread < 0) {
  204. cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
  205. return;
  206. }
  207. int pos = -1;
  208. if (cnc.readState == ReadState.None) {
  209. Exception exc = null;
  210. try {
  211. pos = cnc.GetResponse (cnc.buffer, nread);
  212. if (data.StatusCode == 100) {
  213. cnc.readState = ReadState.None;
  214. InitRead (cnc);
  215. cnc.sPoint.SendContinue = true;
  216. if (cnc.waitingForContinue) {
  217. cnc.waitForContinue.Set ();
  218. } else if (data.request.ExpectContinue) { // We get a 100 after waiting for it.
  219. data.request.DoContinueDelegate (data.StatusCode, data.Headers);
  220. }
  221. return;
  222. }
  223. } catch (Exception e) {
  224. exc = e;
  225. }
  226. if (pos == -1 || exc != null) {
  227. cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
  228. return;
  229. }
  230. }
  231. if (cnc.readState != ReadState.Content) {
  232. cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
  233. return;
  234. }
  235. 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);
  244. } else {
  245. cnc.chunkStream.ResetBuffer ();
  246. cnc.chunkStream.Write (cnc.buffer, pos, nread);
  247. }
  248. data.stream = stream;
  249. lock (cnc) {
  250. if (cnc.queue.Count > 0)
  251. stream.ReadAll ();
  252. else
  253. {
  254. cnc.prevStream = stream;
  255. stream.CheckComplete ();
  256. }
  257. }
  258. data.request.SetResponseData (data);
  259. }
  260. internal void GetCertificates ()
  261. {
  262. // here the SSL negotiation have been done
  263. X509Certificate client = (X509Certificate) piClient.GetValue (nstream, null);
  264. X509Certificate server = (X509Certificate) piServer.GetValue (nstream, null);
  265. sPoint.SetCertificates (client, server);
  266. certsAvailable = (server != null);
  267. }
  268. static void InitRead (object state)
  269. {
  270. WebConnection cnc = (WebConnection) state;
  271. Stream ns = cnc.nstream;
  272. try {
  273. ns.BeginRead (cnc.buffer, 0, cnc.buffer.Length, readDoneDelegate, cnc);
  274. } catch (Exception e) {
  275. cnc.HandleError (WebExceptionStatus.ReceiveFailure, e);
  276. }
  277. }
  278. int GetResponse (byte [] buffer, int max)
  279. {
  280. int pos = 0;
  281. string line = null;
  282. bool lineok = false;
  283. bool isContinue = false;
  284. do {
  285. if (readState == ReadState.None) {
  286. lineok = ReadLine (buffer, ref pos, max, ref line);
  287. if (!lineok)
  288. return -1;
  289. readState = ReadState.Status;
  290. string [] parts = line.Split (' ');
  291. if (parts.Length < 3)
  292. return -1;
  293. if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
  294. Data.Version = HttpVersion.Version11;
  295. sPoint.SetVersion (HttpVersion.Version11);
  296. } else {
  297. Data.Version = HttpVersion.Version10;
  298. sPoint.SetVersion (HttpVersion.Version10);
  299. }
  300. Data.StatusCode = (int) UInt32.Parse (parts [1]);
  301. Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
  302. if (pos >= max)
  303. return pos;
  304. }
  305. if (readState == ReadState.Status) {
  306. readState = ReadState.Headers;
  307. Data.Headers = new WebHeaderCollection ();
  308. ArrayList headers = new ArrayList ();
  309. bool finished = false;
  310. while (!finished) {
  311. if (ReadLine (buffer, ref pos, max, ref line) == false)
  312. break;
  313. if (line == null) {
  314. // Empty line: end of headers
  315. finished = true;
  316. continue;
  317. }
  318. if (line.Length > 0 && (line [0] == ' ' || line [0] == '\t')) {
  319. int count = headers.Count - 1;
  320. if (count < 0)
  321. break;
  322. string prev = (string) headers [count] + line;
  323. headers [count] = prev;
  324. } else {
  325. headers.Add (line);
  326. }
  327. }
  328. if (!finished) {
  329. // handle the error...
  330. } else {
  331. foreach (string s in headers)
  332. Data.Headers.Add (s);
  333. if (Data.StatusCode == (int) HttpStatusCode.Continue) {
  334. sPoint.SendContinue = true;
  335. if (pos >= max)
  336. return pos;
  337. if (Data.request.ExpectContinue)
  338. Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
  339. readState = ReadState.None;
  340. isContinue = true;
  341. }
  342. else {
  343. readState = ReadState.Content;
  344. return pos;
  345. }
  346. }
  347. }
  348. } while (isContinue == true);
  349. return -1;
  350. }
  351. void InitConnection (object state, bool notUsed)
  352. {
  353. HttpWebRequest request = (HttpWebRequest) state;
  354. if (status == WebExceptionStatus.RequestCanceled) {
  355. busy = false;
  356. Data = new WebConnectionData ();
  357. goAhead.Set ();
  358. SendNext ();
  359. return;
  360. }
  361. keepAlive = request.KeepAlive;
  362. Data = new WebConnectionData ();
  363. Data.request = request;
  364. Connect ();
  365. if (status != WebExceptionStatus.Success) {
  366. request.SetWriteStreamError (status);
  367. Close (true);
  368. return;
  369. }
  370. if (!CreateStream (request)) {
  371. request.SetWriteStreamError (status);
  372. Close (true);
  373. return;
  374. }
  375. readState = ReadState.None;
  376. request.SetWriteStream (new WebConnectionStream (this, request));
  377. InitRead (this);
  378. }
  379. internal EventHandler SendRequest (HttpWebRequest request)
  380. {
  381. lock (this) {
  382. if (prevStream != null && socket != null && socket.Connected) {
  383. prevStream.ReadAll ();
  384. prevStream = null;
  385. }
  386. if (!busy) {
  387. busy = true;
  388. ThreadPool.RegisterWaitForSingleObject (goAhead, initConn,
  389. request, -1, true);
  390. } else {
  391. queue.Enqueue (request);
  392. }
  393. }
  394. return abortHandler;
  395. }
  396. void SendNext ()
  397. {
  398. lock (this) {
  399. if (queue.Count > 0) {
  400. prevStream = null;
  401. SendRequest ((HttpWebRequest) queue.Dequeue ());
  402. }
  403. }
  404. }
  405. internal void NextRead ()
  406. {
  407. lock (this) {
  408. busy = false;
  409. string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
  410. string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
  411. bool keepAlive = (Data.Version == HttpVersion.Version11);
  412. if (cncHeader != null) {
  413. cncHeader = cncHeader.ToLower ();
  414. keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
  415. }
  416. if ((socket != null && !socket.Connected) ||
  417. (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
  418. Close (false);
  419. }
  420. goAhead.Set ();
  421. if (queue.Count > 0) {
  422. prevStream = null;
  423. SendRequest ((HttpWebRequest) queue.Dequeue ());
  424. }
  425. }
  426. }
  427. static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
  428. {
  429. bool foundCR = false;
  430. StringBuilder text = new StringBuilder ();
  431. int c = 0;
  432. while (start < max) {
  433. c = (int) buffer [start++];
  434. if (c == '\n') { // newline
  435. if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
  436. text.Length--;
  437. foundCR = false;
  438. break;
  439. } else if (foundCR) {
  440. text.Length--;
  441. break;
  442. }
  443. if (c == '\r')
  444. foundCR = true;
  445. text.Append ((char) c);
  446. }
  447. if (c != '\n' && c != '\r')
  448. return false;
  449. if (text.Length == 0) {
  450. output = null;
  451. return (c == '\n' || c == '\r');
  452. }
  453. if (foundCR)
  454. text.Length--;
  455. output = text.ToString ();
  456. return true;
  457. }
  458. internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
  459. {
  460. if (nstream == null)
  461. return null;
  462. IAsyncResult result = null;
  463. if (!chunkedRead || chunkStream.WantMore) {
  464. try {
  465. result = nstream.BeginRead (buffer, offset, size, cb, state);
  466. } catch (Exception) {
  467. status = WebExceptionStatus.ReceiveFailure;
  468. throw;
  469. }
  470. }
  471. if (chunkedRead) {
  472. WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
  473. wr.InnerAsyncResult = result;
  474. return wr;
  475. }
  476. return result;
  477. }
  478. internal int EndRead (IAsyncResult result)
  479. {
  480. if (nstream == null)
  481. return 0;
  482. if (chunkedRead) {
  483. WebAsyncResult wr = (WebAsyncResult) result;
  484. int nbytes = 0;
  485. if (wr.InnerAsyncResult != null)
  486. nbytes = nstream.EndRead (wr.InnerAsyncResult);
  487. chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
  488. if (nbytes == 0 && chunkStream.WantMore) {
  489. nbytes = nstream.Read (wr.Buffer, wr.Offset, wr.Size);
  490. chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
  491. }
  492. return nbytes;
  493. }
  494. return nstream.EndRead (result);
  495. }
  496. internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
  497. {
  498. IAsyncResult result = null;
  499. if (nstream == null)
  500. return null;
  501. try {
  502. result = nstream.BeginWrite (buffer, offset, size, cb, state);
  503. } catch (Exception) {
  504. status = WebExceptionStatus.SendFailure;
  505. throw;
  506. }
  507. return result;
  508. }
  509. internal void EndWrite (IAsyncResult result)
  510. {
  511. if (nstream != null)
  512. nstream.EndWrite (result);
  513. }
  514. internal int Read (byte [] buffer, int offset, int size)
  515. {
  516. if (nstream == null)
  517. return 0;
  518. int result = 0;
  519. try {
  520. if (!chunkedRead || chunkStream.WantMore)
  521. result = nstream.Read (buffer, offset, size);
  522. if (chunkedRead)
  523. chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
  524. } catch (Exception e) {
  525. status = WebExceptionStatus.ReceiveFailure;
  526. HandleError (status, e);
  527. }
  528. return result;
  529. }
  530. internal void Write (byte [] buffer, int offset, int size)
  531. {
  532. if (nstream == null)
  533. return;
  534. try {
  535. nstream.Write (buffer, offset, size);
  536. // here SSL handshake should have been done
  537. if (ssl && !certsAvailable) {
  538. GetCertificates ();
  539. }
  540. } catch (Exception) {
  541. }
  542. }
  543. internal bool TryReconnect ()
  544. {
  545. lock (this) {
  546. if (!reused) {
  547. HandleError (WebExceptionStatus.SendFailure, null);
  548. return false;
  549. }
  550. Close (false);
  551. reused = false;
  552. Connect ();
  553. if (status != WebExceptionStatus.Success) {
  554. HandleError (WebExceptionStatus.SendFailure, null);
  555. return false;
  556. }
  557. if (!CreateStream (Data.request)) {
  558. HandleError (WebExceptionStatus.SendFailure, null);
  559. return false;
  560. }
  561. }
  562. return true;
  563. }
  564. void Close (bool sendNext)
  565. {
  566. lock (this) {
  567. busy = false;
  568. if (nstream != null) {
  569. try {
  570. nstream.Close ();
  571. } catch {}
  572. nstream = null;
  573. }
  574. if (socket != null) {
  575. try {
  576. socket.Close ();
  577. } catch {}
  578. socket = null;
  579. }
  580. if (sendNext) {
  581. goAhead.Set ();
  582. SendNext ();
  583. }
  584. }
  585. }
  586. void Abort (object sender, EventArgs args)
  587. {
  588. HandleError (WebExceptionStatus.RequestCanceled, null);
  589. }
  590. internal bool Busy {
  591. get { lock (this) return busy; }
  592. }
  593. internal bool Connected {
  594. get {
  595. lock (this) {
  596. return (socket != null && socket.Connected);
  597. }
  598. }
  599. }
  600. ~WebConnection ()
  601. {
  602. Close (false);
  603. }
  604. }
  605. }