HttpRequest.cs 15 KB

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