HttpRequest.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. using System;
  2. using System.Collections.Specialized;
  3. using System.IO;
  4. using System.Net;
  5. using System.Text;
  6. using System.Web;
  7. using OSHttpServer.Exceptions;
  8. namespace OSHttpServer
  9. {
  10. /// <summary>
  11. /// Contains server side HTTP request information.
  12. /// </summary>
  13. public class HttpRequest : IHttpRequest
  14. {
  15. /// <summary>
  16. /// Chars used to split an URL path into multiple parts.
  17. /// </summary>
  18. public static readonly char[] UriSplitters = new[] { '/' };
  19. public static uint baseID = 0;
  20. private readonly NameValueCollection m_headers = new NameValueCollection();
  21. private readonly HttpParam m_param = new HttpParam(HttpInput.Empty, HttpInput.Empty);
  22. private Stream m_body = new MemoryStream();
  23. private int m_bodyBytesLeft;
  24. private ConnectionType m_connection = ConnectionType.KeepAlive;
  25. private int m_contentLength;
  26. private string m_httpVersion = string.Empty;
  27. private string m_method = string.Empty;
  28. private NameValueCollection m_queryString = null;
  29. private Uri m_uri = null;
  30. private string m_uriPath;
  31. public readonly IHttpClientContext m_context;
  32. IPEndPoint m_remoteIPEndPoint = null;
  33. public HttpRequest(IHttpClientContext pContext)
  34. {
  35. ID = ++baseID;
  36. m_context = pContext;
  37. }
  38. public uint ID { get; private set; }
  39. /// <summary>
  40. /// Gets or sets a value indicating whether this <see cref="HttpRequest"/> is secure.
  41. /// </summary>
  42. public bool Secure { get { return m_context.IsSecured; } }
  43. public IHttpClientContext Context { get { return m_context; } }
  44. /// <summary>
  45. /// Path and query (will be merged with the host header) and put in Uri
  46. /// </summary>
  47. /// <see cref="Uri"/>
  48. public string UriPath
  49. {
  50. get { return m_uriPath; }
  51. set { m_uriPath = value; }
  52. }
  53. /// <summary>
  54. /// Assign a form.
  55. /// </summary>
  56. /// <param name="form"></param>
  57. /*
  58. internal void AssignForm(HttpForm form)
  59. {
  60. _form = form;
  61. }
  62. */
  63. #region IHttpRequest Members
  64. /// <summary>
  65. /// Gets kind of types accepted by the client.
  66. /// </summary>
  67. public string[] AcceptTypes { get; private set; }
  68. /// <summary>
  69. /// Gets or sets body stream.
  70. /// </summary>
  71. public Stream Body
  72. {
  73. get { return m_body; }
  74. set { m_body = value; }
  75. }
  76. /// <summary>
  77. /// Gets or sets kind of connection used for the session.
  78. /// </summary>
  79. public ConnectionType Connection
  80. {
  81. get { return m_connection; }
  82. set { m_connection = value; }
  83. }
  84. /// <summary>
  85. /// Gets or sets number of bytes in the body.
  86. /// </summary>
  87. public int ContentLength
  88. {
  89. get { return m_contentLength; }
  90. set
  91. {
  92. m_contentLength = value;
  93. m_bodyBytesLeft = value;
  94. }
  95. }
  96. /// <summary>
  97. /// Gets headers sent by the client.
  98. /// </summary>
  99. public NameValueCollection Headers
  100. {
  101. get { return m_headers; }
  102. }
  103. /// <summary>
  104. /// Gets or sets version of HTTP protocol that's used.
  105. /// </summary>
  106. /// <remarks>
  107. /// Probably <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/>.
  108. /// </remarks>
  109. /// <seealso cref="HttpHelper"/>
  110. public string HttpVersion
  111. {
  112. get { return m_httpVersion; }
  113. set { m_httpVersion = value; }
  114. }
  115. /// <summary>
  116. /// Gets or sets requested method.
  117. /// </summary>
  118. /// <value></value>
  119. /// <remarks>
  120. /// Will always be in upper case.
  121. /// </remarks>
  122. /// <see cref="OSHttpServer.Method"/>
  123. public string Method
  124. {
  125. get { return m_method; }
  126. set { m_method = value; }
  127. }
  128. /// <summary>
  129. /// Gets variables sent in the query string
  130. /// </summary>
  131. public NameValueCollection QueryString
  132. {
  133. get
  134. {
  135. if(m_queryString == null)
  136. {
  137. if(m_uri == null || m_uri.Query.Length == 0)
  138. m_queryString = new NameValueCollection();
  139. else
  140. {
  141. try
  142. {
  143. m_queryString = HttpUtility.ParseQueryString(m_uri.Query);
  144. }
  145. catch { m_queryString = new NameValueCollection(); }
  146. }
  147. }
  148. return m_queryString;
  149. }
  150. }
  151. public static readonly Uri EmptyUri = new Uri("http://localhost/");
  152. /// <summary>
  153. /// Gets or sets requested URI.
  154. /// </summary>
  155. public Uri Uri
  156. {
  157. get { return m_uri; }
  158. set { m_uri = value ?? EmptyUri; } // not safe
  159. }
  160. /// <summary>
  161. /// Gets parameter from <see cref="QueryString"/> or <see cref="Form"/>.
  162. /// </summary>
  163. public HttpParam Param
  164. {
  165. get { return m_param; }
  166. }
  167. /// <summary>
  168. /// Gets form parameters.
  169. /// </summary>
  170. /*
  171. public HttpForm Form
  172. {
  173. get { return _form; }
  174. }
  175. */
  176. /// <summary>
  177. /// Gets whether the request was made by Ajax (Asynchronous JavaScript)
  178. /// </summary>
  179. public bool IsAjax { get; private set; }
  180. /// <summary>
  181. /// Gets cookies that was sent with the request.
  182. /// </summary>
  183. public RequestCookies Cookies { get; private set; }
  184. public double ArrivalTS { get; set;}
  185. ///<summary>
  186. ///Creates a new object that is a copy of the current instance.
  187. ///</summary>
  188. ///
  189. ///<returns>
  190. ///A new object that is a copy of this instance.
  191. ///</returns>
  192. ///<filterpriority>2</filterpriority>
  193. public object Clone()
  194. {
  195. // this method was mainly created for testing.
  196. // dont use it that much...
  197. var request = new HttpRequest(Context);
  198. request.Method = m_method;
  199. if (AcceptTypes != null)
  200. {
  201. request.AcceptTypes = new string[AcceptTypes.Length];
  202. AcceptTypes.CopyTo(request.AcceptTypes, 0);
  203. }
  204. request.m_httpVersion = m_httpVersion;
  205. request.m_queryString = m_queryString;
  206. request.Uri = m_uri;
  207. var buffer = new byte[m_body.Length];
  208. m_body.Read(buffer, 0, (int)m_body.Length);
  209. request.Body = new MemoryStream();
  210. request.Body.Write(buffer, 0, buffer.Length);
  211. request.Body.Seek(0, SeekOrigin.Begin);
  212. request.Body.Flush();
  213. request.m_headers.Clear();
  214. foreach (string key in m_headers)
  215. {
  216. string[] values = m_headers.GetValues(key);
  217. if (values != null)
  218. foreach (string value in values)
  219. request.AddHeader(key, value);
  220. }
  221. return request;
  222. }
  223. /// <summary>
  224. /// Decode body into a form.
  225. /// </summary>
  226. /// <param name="providers">A list with form decoders.</param>
  227. /// <exception cref="InvalidDataException">If body contents is not valid for the chosen decoder.</exception>
  228. /// <exception cref="InvalidOperationException">If body is still being transferred.</exception>
  229. /*
  230. public void DecodeBody(FormDecoderProvider providers)
  231. {
  232. if (_bodyBytesLeft > 0)
  233. throw new InvalidOperationException("Body have not yet been completed.");
  234. _form = providers.Decode(_headers["content-type"], _body, Encoding.UTF8);
  235. if (_form != HttpInput.Empty)
  236. _param.SetForm(_form);
  237. }
  238. */
  239. ///<summary>
  240. /// Cookies
  241. ///</summary>
  242. ///<param name="cookies">the cookies</param>
  243. public void SetCookies(RequestCookies cookies)
  244. {
  245. Cookies = cookies;
  246. }
  247. public IPEndPoint LocalIPEndPoint { get {return m_context.LocalIPEndPoint; }}
  248. public IPEndPoint RemoteIPEndPoint
  249. {
  250. get
  251. {
  252. if(m_remoteIPEndPoint == null)
  253. {
  254. string addr = m_headers["x-forwarded-for"];
  255. if(!string.IsNullOrEmpty(addr))
  256. {
  257. int port = m_context.LocalIPEndPoint.Port;
  258. try
  259. {
  260. m_remoteIPEndPoint = new IPEndPoint(IPAddress.Parse(addr), port);
  261. }
  262. catch
  263. {
  264. m_remoteIPEndPoint = null;
  265. }
  266. }
  267. }
  268. if (m_remoteIPEndPoint == null)
  269. m_remoteIPEndPoint = m_context.LocalIPEndPoint;
  270. return m_remoteIPEndPoint;
  271. }
  272. }
  273. /*
  274. /// <summary>
  275. /// Create a response object.
  276. /// </summary>
  277. /// <returns>A new <see cref="IHttpResponse"/>.</returns>
  278. public IHttpResponse CreateResponse(IHttpClientContext context)
  279. {
  280. return new HttpResponse(context, this);
  281. }
  282. */
  283. /// <summary>
  284. /// Called during parsing of a <see cref="IHttpRequest"/>.
  285. /// </summary>
  286. /// <param name="name">Name of the header, should not be URL encoded</param>
  287. /// <param name="value">Value of the header, should not be URL encoded</param>
  288. /// <exception cref="BadRequestException">If a header is incorrect.</exception>
  289. public void AddHeader(string name, string value)
  290. {
  291. if (string.IsNullOrEmpty(name))
  292. throw new BadRequestException("Invalid header name: " + name ?? "<null>");
  293. if (string.IsNullOrEmpty(value))
  294. throw new BadRequestException("Header '" + name + "' do not contain a value.");
  295. name = name.ToLowerInvariant();
  296. switch (name)
  297. {
  298. case "http_x_requested_with":
  299. case "x-requested-with":
  300. if (string.Compare(value, "XMLHttpRequest", true) == 0)
  301. IsAjax = true;
  302. break;
  303. case "accept":
  304. AcceptTypes = value.Split(',');
  305. for (int i = 0; i < AcceptTypes.Length; ++i)
  306. AcceptTypes[i] = AcceptTypes[i].Trim();
  307. break;
  308. case "content-length":
  309. if (!int.TryParse(value, out int t))
  310. throw new BadRequestException("Invalid content length.");
  311. ContentLength = t;
  312. break; //todo: maybe throw an exception
  313. case "host":
  314. try
  315. {
  316. m_uri = new Uri((Secure ? "https://" : "http://") + value + m_uriPath);
  317. m_uriPath = m_uri.AbsolutePath;
  318. }
  319. catch (UriFormatException err)
  320. {
  321. throw new BadRequestException("Failed to parse uri: " + value + m_uriPath, err);
  322. }
  323. break;
  324. case "remote_addr":
  325. if (m_headers[name] == null)
  326. m_headers.Add(name, value);
  327. break;
  328. case "forwarded":
  329. string[] parts = value.Split(new char[]{';'});
  330. string addr = string.Empty;
  331. for(int i = 0; i < parts.Length; ++i)
  332. {
  333. string s = parts[i].TrimStart();
  334. if(s.Length < 10)
  335. continue;
  336. if(s.StartsWith("for", StringComparison.InvariantCultureIgnoreCase))
  337. {
  338. int indx = s.IndexOf("=", 3);
  339. if(indx < 0 || indx >= s.Length - 1)
  340. continue;
  341. s = s.Substring(indx);
  342. addr = s.Trim();
  343. }
  344. }
  345. if(addr.Length > 7)
  346. {
  347. m_headers.Add("x-forwarded-for", addr);
  348. }
  349. break;
  350. case "x-forwarded-for":
  351. if (value.Length > 7)
  352. {
  353. string[] xparts = value.Split(new char[]{','});
  354. if(xparts.Length > 0)
  355. {
  356. string xs = xparts[0].Trim();
  357. if(xs.Length > 7)
  358. m_headers.Add("x-forwarded-for", xs);
  359. }
  360. }
  361. break;
  362. case "connection":
  363. if (string.Compare(value, "close", true) == 0)
  364. Connection = ConnectionType.Close;
  365. else if (value.StartsWith("keep-alive", StringComparison.CurrentCultureIgnoreCase))
  366. Connection = ConnectionType.KeepAlive;
  367. else if (value.StartsWith("Upgrade", StringComparison.CurrentCultureIgnoreCase))
  368. Connection = ConnectionType.KeepAlive;
  369. else
  370. throw new BadRequestException("Unknown 'Connection' header type.");
  371. break;
  372. /*
  373. case "expect":
  374. if (value.Contains("100-continue"))
  375. {
  376. }
  377. m_headers.Add(name, value);
  378. break;
  379. */
  380. case "user-agent":
  381. break;
  382. default:
  383. m_headers.Add(name, value);
  384. break;
  385. }
  386. }
  387. /// <summary>
  388. /// Add bytes to the body
  389. /// </summary>
  390. /// <param name="bytes">buffer to read bytes from</param>
  391. /// <param name="offset">where to start read</param>
  392. /// <param name="length">number of bytes to read</param>
  393. /// <returns>Number of bytes actually read (same as length unless we got all body bytes).</returns>
  394. /// <exception cref="InvalidOperationException">If body is not writable</exception>
  395. /// <exception cref="ArgumentNullException"><c>bytes</c> is null.</exception>
  396. /// <exception cref="ArgumentOutOfRangeException"><c>offset</c> is out of range.</exception>
  397. public int AddToBody(byte[] bytes, int offset, int length)
  398. {
  399. if (bytes == null)
  400. throw new ArgumentNullException("bytes");
  401. if (offset + length > bytes.Length)
  402. throw new ArgumentOutOfRangeException("offset");
  403. if (length == 0)
  404. return 0;
  405. if (!m_body.CanWrite)
  406. throw new InvalidOperationException("Body is not writable.");
  407. if (length > m_bodyBytesLeft)
  408. {
  409. length = m_bodyBytesLeft;
  410. }
  411. m_body.Write(bytes, offset, length);
  412. m_bodyBytesLeft -= length;
  413. return length;
  414. }
  415. /// <summary>
  416. /// Clear everything in the request
  417. /// </summary>
  418. public void Clear()
  419. {
  420. if (m_body != null && m_body.CanRead)
  421. m_body.Dispose();
  422. m_body = null;
  423. m_contentLength = 0;
  424. m_method = string.Empty;
  425. m_uri = null;
  426. m_queryString = null;
  427. m_bodyBytesLeft = 0;
  428. m_headers.Clear();
  429. m_connection = ConnectionType.KeepAlive;
  430. IsAjax = false;
  431. //_form.Clear();
  432. }
  433. #endregion
  434. }
  435. }