Connection.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // **********************************************************************************
  2. // CassiniDev - http://cassinidev.codeplex.com
  3. //
  4. // Copyright (c) 2010 Sky Sanders. All rights reserved.
  5. // Copyright (c) Microsoft Corporation. All rights reserved.
  6. //
  7. // This source code is subject to terms and conditions of the Microsoft Public
  8. // License (Ms-PL). A copy of the license can be found in the license.txt file
  9. // included in this distribution.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // **********************************************************************************
  14. #region
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Globalization;
  18. using System.IO;
  19. using System.Net;
  20. using System.Net.Sockets;
  21. using System.Text;
  22. using System.Web;
  23. using CassiniDev.ServerLog;
  24. #endregion
  25. namespace CassiniDev
  26. {
  27. public class Connection : MarshalByRefObject
  28. {
  29. private const int HttpForbidden = 403;
  30. private const int HttpOK = 200;
  31. private readonly MemoryStream _responseContent;
  32. public List<string> Plugins = new List<string>();
  33. private readonly Server _server;
  34. private LogInfo _requestLog;
  35. private LogInfo _responseLog;
  36. private Socket _socket;
  37. internal Connection(Server server, Socket socket)
  38. {
  39. Plugins = server.Plugins;
  40. Id = Guid.NewGuid();
  41. _responseContent = new MemoryStream();
  42. _server = server;
  43. _socket = socket;
  44. InitializeLogInfo();
  45. }
  46. public bool Connected
  47. {
  48. get { return _socket.Connected; }
  49. }
  50. public Guid Id { get; private set; }
  51. public string LocalIP
  52. {
  53. get
  54. {
  55. IPEndPoint ep = (IPEndPoint) _socket.LocalEndPoint;
  56. return (ep != null && ep.Address != null) ? ep.Address.ToString() : "127.0.0.1";
  57. }
  58. }
  59. public string RemoteIP
  60. {
  61. get
  62. {
  63. IPEndPoint ep = (IPEndPoint) _socket.RemoteEndPoint;
  64. return (ep != null && ep.Address != null) ? ep.Address.ToString() : "127.0.0.1";
  65. }
  66. }
  67. public LogInfo RequestLog
  68. {
  69. get { return _requestLog; }
  70. }
  71. public LogInfo ResponseLog
  72. {
  73. get { return _responseLog; }
  74. }
  75. public void Close()
  76. {
  77. FinalizeLogInfo();
  78. try
  79. {
  80. _socket.Shutdown(SocketShutdown.Both);
  81. _socket.Close();
  82. }
  83. // ReSharper disable EmptyGeneralCatchClause
  84. catch
  85. // ReSharper restore EmptyGeneralCatchClause
  86. {
  87. }
  88. finally
  89. {
  90. _socket = null;
  91. }
  92. }
  93. /// <summary>
  94. /// </summary>
  95. public override object InitializeLifetimeService()
  96. {
  97. return null;
  98. }
  99. public void LogRequest(string pathTranslated, string url)
  100. {
  101. _requestLog.PathTranslated = pathTranslated;
  102. _requestLog.Url = url;
  103. }
  104. public void LogRequestBody(byte[] content)
  105. {
  106. _requestLog.Body = content;
  107. }
  108. public void LogRequestHeaders(string headers)
  109. {
  110. _requestLog.Headers = headers;
  111. }
  112. public byte[] ReadRequestBytes(int maxBytes)
  113. {
  114. try
  115. {
  116. if (WaitForRequestBytes() == 0)
  117. {
  118. return null;
  119. }
  120. int numBytes = _socket.Available;
  121. if (numBytes > maxBytes)
  122. {
  123. numBytes = maxBytes;
  124. }
  125. int numReceived = 0;
  126. byte[] buffer = new byte[numBytes];
  127. if (numBytes > 0)
  128. {
  129. numReceived = _socket.Receive(buffer, 0, numBytes, SocketFlags.None);
  130. }
  131. if (numReceived < numBytes)
  132. {
  133. byte[] tempBuffer = new byte[numReceived];
  134. if (numReceived > 0)
  135. {
  136. Buffer.BlockCopy(buffer, 0, tempBuffer, 0, numReceived);
  137. }
  138. buffer = tempBuffer;
  139. }
  140. return buffer;
  141. }
  142. catch
  143. {
  144. return null;
  145. }
  146. }
  147. public int WaitForRequestBytes()
  148. {
  149. int availBytes = 0;
  150. try
  151. {
  152. if (_socket.Available == 0)
  153. {
  154. _socket.Poll(100000, SelectMode.SelectRead);
  155. if (_socket.Available == 0 && _socket.Connected)
  156. {
  157. _socket.Poll(30000000, SelectMode.SelectRead);
  158. }
  159. }
  160. availBytes = _socket.Available;
  161. }
  162. // ReSharper disable EmptyGeneralCatchClause
  163. catch
  164. // ReSharper restore EmptyGeneralCatchClause
  165. {
  166. }
  167. return availBytes;
  168. }
  169. public void Write100Continue()
  170. {
  171. WriteEntireResponseFromString(100, null, null, true);
  172. }
  173. internal void Write200Continue()
  174. {
  175. WriteEntireResponseFromString(200, null, string.Empty, true);
  176. }
  177. public void WriteBody(byte[] data, int offset, int length)
  178. {
  179. try
  180. {
  181. _responseContent.Write(data, 0, data.Length);
  182. _socket.Send(data, offset, length, SocketFlags.None);
  183. }
  184. catch (SocketException)
  185. {
  186. }
  187. }
  188. public void WriteEntireResponseFromFile(String fileName, bool keepAlive)
  189. {
  190. if (!File.Exists(fileName))
  191. {
  192. WriteErrorAndClose(404);
  193. return;
  194. }
  195. // Deny the request if the contentType cannot be recognized.
  196. string contentType = CommonExtensions.GetContentType(fileName);
  197. //TODO: i am pretty sure this is unnecessary
  198. if (contentType == null)
  199. {
  200. WriteErrorAndClose(HttpForbidden);
  201. return;
  202. }
  203. string contentTypeHeader = "Content-Type: " + contentType + "\r\n";
  204. bool completed = false;
  205. FileStream fs = null;
  206. try
  207. {
  208. fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
  209. int len = (int) fs.Length;
  210. byte[] fileBytes = new byte[len];
  211. int bytesRead = fs.Read(fileBytes, 0, len);
  212. String headers = MakeResponseHeaders(HttpOK, contentTypeHeader, bytesRead, keepAlive);
  213. _responseLog.Headers = headers;
  214. _responseLog.StatusCode = HttpOK;
  215. _socket.Send(Encoding.UTF8.GetBytes(headers));
  216. _socket.Send(fileBytes, 0, bytesRead, SocketFlags.None);
  217. completed = true;
  218. }
  219. catch (SocketException)
  220. {
  221. }
  222. finally
  223. {
  224. if (!keepAlive || !completed)
  225. {
  226. Close();
  227. }
  228. if (fs != null)
  229. {
  230. fs.Close();
  231. }
  232. }
  233. }
  234. public void WriteEntireResponseFromString(int statusCode, String extraHeaders, String body, bool keepAlive)
  235. {
  236. try
  237. {
  238. int bodyLength = (body != null) ? Encoding.UTF8.GetByteCount(body) : 0;
  239. string headers = MakeResponseHeaders(statusCode, extraHeaders, bodyLength, keepAlive);
  240. _responseLog.Headers = headers;
  241. _responseLog.StatusCode = statusCode;
  242. _socket.Send(Encoding.UTF8.GetBytes(headers + body));
  243. }
  244. catch (SocketException)
  245. {
  246. }
  247. finally
  248. {
  249. if (!keepAlive)
  250. {
  251. Close();
  252. }
  253. }
  254. }
  255. public void WriteErrorAndClose(int statusCode, string message)
  256. {
  257. WriteEntireResponseFromString(statusCode, null, GetErrorResponseBody(statusCode, message), false);
  258. }
  259. public void WriteErrorAndClose(int statusCode)
  260. {
  261. WriteErrorAndClose(statusCode, null);
  262. }
  263. public void WriteErrorWithExtraHeadersAndKeepAlive(int statusCode, string extraHeaders)
  264. {
  265. WriteEntireResponseFromString(statusCode, extraHeaders, GetErrorResponseBody(statusCode, null), true);
  266. }
  267. public void WriteHeaders(int statusCode, String extraHeaders)
  268. {
  269. string headers = MakeResponseHeaders(statusCode, extraHeaders, -1, false);
  270. _responseLog.Headers = headers;
  271. _responseLog.StatusCode = statusCode;
  272. try
  273. {
  274. _socket.Send(Encoding.UTF8.GetBytes(headers));
  275. }
  276. catch (SocketException)
  277. {
  278. }
  279. }
  280. private void FinalizeLogInfo()
  281. {
  282. try
  283. {
  284. _responseLog.Body = _responseContent.ToArray();
  285. _responseContent.Dispose();
  286. _responseLog.Created = DateTime.Now;
  287. _responseLog.Url = _requestLog.Url;
  288. _responseLog.PathTranslated = _requestLog.PathTranslated;
  289. _responseLog.Identity = _requestLog.Identity;
  290. _responseLog.PhysicalPath = _requestLog.PhysicalPath;
  291. }
  292. // ReSharper disable EmptyGeneralCatchClause
  293. catch
  294. // ReSharper restore EmptyGeneralCatchClause
  295. {
  296. // log error to text
  297. }
  298. }
  299. private string GetErrorResponseBody(int statusCode, string message)
  300. {
  301. string body = Messages.FormatErrorMessageBody(statusCode, _server.VirtualPath);
  302. if (!string.IsNullOrEmpty(message))
  303. {
  304. body += "\r\n<!--\r\n" + message + "\r\n-->";
  305. }
  306. return body;
  307. }
  308. private void InitializeLogInfo()
  309. {
  310. _requestLog = new LogInfo
  311. {
  312. Created = DateTime.Now,
  313. ConversationId = Id,
  314. RowType = 1,
  315. Identity = _server.GetProcessUser(),
  316. PhysicalPath = _server.PhysicalPath
  317. };
  318. _responseLog = new LogInfo
  319. {
  320. ConversationId = Id,
  321. RowType = 2
  322. };
  323. }
  324. private static string MakeResponseHeaders(int statusCode, string moreHeaders, int contentLength, bool keepAlive)
  325. {
  326. StringBuilder sb = new StringBuilder();
  327. sb.Append("HTTP/1.1 " + statusCode + " " + HttpWorkerRequest.GetStatusDescription(statusCode) + "\r\n");
  328. sb.Append("Server: Cassini/" + Messages.VersionString + "\r\n");
  329. sb.Append("Date: " + DateTime.Now.ToUniversalTime().ToString("R", DateTimeFormatInfo.InvariantInfo) + "\r\n");
  330. if (contentLength >= 0)
  331. {
  332. sb.Append("Content-Length: " + contentLength + "\r\n");
  333. }
  334. if (moreHeaders != null)
  335. {
  336. sb.Append(moreHeaders);
  337. }
  338. if (!keepAlive)
  339. {
  340. sb.Append("Connection: Close\r\n");
  341. }
  342. sb.Append("\r\n");
  343. return sb.ToString();
  344. }
  345. }
  346. }