HttpRequest.cs 16 KB

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