HttpResponse.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Text;
  5. namespace NetCoreServer
  6. {
  7. /// <summary>
  8. /// HTTP response is used to create or process parameters of HTTP protocol response(status, headers, etc).
  9. /// </summary>
  10. /// <remarks>Not thread-safe.</remarks>
  11. public class HttpResponse
  12. {
  13. static HttpResponse()
  14. {
  15. _mimeTable = new Dictionary<string, string>
  16. {
  17. // Base content types
  18. { ".html", "text/html" },
  19. { ".css", "text/css" },
  20. { ".js", "text/javascript" },
  21. { ".vue", "text/html" },
  22. { ".xml", "text/xml" },
  23. // Application content types
  24. { ".atom", "application/atom+xml" },
  25. { ".fastsoap", "application/fastsoap" },
  26. { ".gzip", "application/gzip" },
  27. { ".json", "application/json" },
  28. { ".map", "application/json" },
  29. { ".pdf", "application/pdf" },
  30. { ".ps", "application/postscript" },
  31. { ".soap", "application/soap+xml" },
  32. { ".sql", "application/sql" },
  33. { ".xslt", "application/xslt+xml" },
  34. { ".zip", "application/zip" },
  35. { ".zlib", "application/zlib" },
  36. // Audio content types
  37. { ".aac", "audio/aac" },
  38. { ".ac3", "audio/ac3" },
  39. { ".mp3", "audio/mpeg" },
  40. { ".ogg", "audio/ogg" },
  41. // Font content types
  42. { ".ttf", "font/ttf" },
  43. // Image content types
  44. { ".bmp", "image/bmp" },
  45. { ".emf", "image/emf" },
  46. { ".gif", "image/gif" },
  47. { ".jpg", "image/jpeg" },
  48. { ".jpm", "image/jpm" },
  49. { ".jpx", "image/jpx" },
  50. { ".jrx", "image/jrx" },
  51. { ".png", "image/png" },
  52. { ".svg", "image/svg+xml" },
  53. { ".tiff", "image/tiff" },
  54. { ".wmf", "image/wmf" },
  55. // Message content types
  56. { ".http", "message/http" },
  57. { ".s-http", "message/s-http" },
  58. // Model content types
  59. { ".mesh", "model/mesh" },
  60. { ".vrml", "model/vrml" },
  61. // Text content types
  62. { ".csv", "text/csv" },
  63. { ".plain", "text/plain" },
  64. { ".richtext", "text/richtext" },
  65. { ".rtf", "text/rtf" },
  66. { ".rtx", "text/rtx" },
  67. { ".sgml", "text/sgml" },
  68. { ".strings", "text/strings" },
  69. { ".url", "text/uri-list" },
  70. // Video content types
  71. { ".H264", "video/H264" },
  72. { ".H265", "video/H265" },
  73. { ".mp4", "video/mp4" },
  74. { ".mpeg", "video/mpeg" },
  75. { ".raw", "video/raw" }
  76. };
  77. }
  78. /// <summary>
  79. /// Initialize an empty HTTP response
  80. /// </summary>
  81. public HttpResponse()
  82. {
  83. Clear();
  84. }
  85. /// <summary>
  86. /// Initialize a new HTTP response with a given status and protocol
  87. /// </summary>
  88. /// <param name="status">HTTP status</param>
  89. /// <param name="protocol">Protocol version (default is "HTTP/1.1")</param>
  90. public HttpResponse(int status, string protocol = "HTTP/1.1")
  91. {
  92. SetBegin(status, protocol);
  93. }
  94. /// <summary>
  95. /// Initialize a new HTTP response with a given status, status phrase and protocol
  96. /// </summary>
  97. /// <param name="status">HTTP status</param>
  98. /// <param name="statusPhrase">HTTP status phrase</param>
  99. /// <param name="protocol">Protocol version</param>
  100. public HttpResponse(int status, string statusPhrase, string protocol)
  101. {
  102. SetBegin(status, statusPhrase, protocol);
  103. }
  104. /// <summary>
  105. /// Is the HTTP response empty?
  106. /// </summary>
  107. public bool IsEmpty { get { return (_cache.Size > 0); } }
  108. /// <summary>
  109. /// Is the HTTP response error flag set?
  110. /// </summary>
  111. public bool IsErrorSet { get; private set; }
  112. /// <summary>
  113. /// Get the HTTP response status
  114. /// </summary>
  115. public int Status { get; private set; }
  116. /// <summary>
  117. /// Get the HTTP response status phrase
  118. /// </summary>
  119. public string StatusPhrase { get { return _statusPhrase; } }
  120. /// <summary>
  121. /// Get the HTTP response protocol version
  122. /// </summary>
  123. public string Protocol { get { return _protocol; } }
  124. /// <summary>
  125. /// Get the HTTP response headers count
  126. /// </summary>
  127. public long Headers { get { return _headers.Count; } }
  128. /// <summary>
  129. /// Get the HTTP response header by index
  130. /// </summary>
  131. public (string, string) Header(int i)
  132. {
  133. Debug.Assert((i < _headers.Count), "Index out of bounds!");
  134. if (i >= _headers.Count)
  135. return ("", "");
  136. return _headers[i];
  137. }
  138. /// <summary>
  139. /// Get the HTTP response body as string
  140. /// </summary>
  141. public string Body { get { return _cache.ExtractString(_bodyIndex, _bodySize); } }
  142. /// <summary>
  143. /// Get the HTTP request body as byte array
  144. /// </summary>
  145. public byte[] BodyBytes { get { return _cache.Data[_bodyIndex..(_bodyIndex + _bodySize)]; } }
  146. /// <summary>
  147. /// Get the HTTP request body as read-only byte span
  148. /// </summary>
  149. public ReadOnlySpan<byte> BodySpan { get { return new ReadOnlySpan<byte>(_cache.Data, _bodyIndex, _bodySize); } }
  150. /// <summary>
  151. /// Get the HTTP response body length
  152. /// </summary>
  153. public long BodyLength { get { return _bodyLength; } }
  154. /// <summary>
  155. /// Get the HTTP response cache content
  156. /// </summary>
  157. public Buffer Cache { get { return _cache; } }
  158. /// <summary>
  159. /// Get string from the current HTTP response
  160. /// </summary>
  161. public override string ToString()
  162. {
  163. StringBuilder sb = new StringBuilder();
  164. sb.AppendLine($"Status: {Status}");
  165. sb.AppendLine($"Status phrase: {StatusPhrase}");
  166. sb.AppendLine($"Protocol: {Protocol}");
  167. sb.AppendLine($"Headers: {Headers}");
  168. for (int i = 0; i < Headers; i++)
  169. {
  170. var header = Header(i);
  171. sb.AppendLine($"{header.Item1} : {header.Item2}");
  172. }
  173. sb.AppendLine($"Body: {BodyLength}");
  174. sb.AppendLine(Body);
  175. return sb.ToString();
  176. }
  177. /// <summary>
  178. /// Clear the HTTP response cache
  179. /// </summary>
  180. public HttpResponse Clear()
  181. {
  182. IsErrorSet = false;
  183. Status = 0;
  184. _statusPhrase = "";
  185. _protocol = "";
  186. _headers.Clear();
  187. _bodyIndex = 0;
  188. _bodySize = 0;
  189. _bodyLength = 0;
  190. _bodyLengthProvided = false;
  191. _cache.Clear();
  192. _cacheSize = 0;
  193. return this;
  194. }
  195. /// <summary>
  196. /// Set the HTTP response begin with a given status and protocol
  197. /// </summary>
  198. /// <param name="status">HTTP status</param>
  199. /// <param name="protocol">Protocol version (default is "HTTP/1.1")</param>
  200. public HttpResponse SetBegin(int status, string protocol = "HTTP/1.1")
  201. {
  202. string statusPhrase;
  203. switch (status)
  204. {
  205. case 100: statusPhrase = "Continue"; break;
  206. case 101: statusPhrase = "Switching Protocols"; break;
  207. case 102: statusPhrase = "Processing"; break;
  208. case 103: statusPhrase = "Early Hints"; break;
  209. case 200: statusPhrase = "OK"; break;
  210. case 201: statusPhrase = "Created"; break;
  211. case 202: statusPhrase = "Accepted"; break;
  212. case 203: statusPhrase = "Non-Authoritative Information"; break;
  213. case 204: statusPhrase = "No Content"; break;
  214. case 205: statusPhrase = "Reset Content"; break;
  215. case 206: statusPhrase = "Partial Content"; break;
  216. case 207: statusPhrase = "Multi-Status"; break;
  217. case 208: statusPhrase = "Already Reported"; break;
  218. case 226: statusPhrase = "IM Used"; break;
  219. case 300: statusPhrase = "Multiple Choices"; break;
  220. case 301: statusPhrase = "Moved Permanently"; break;
  221. case 302: statusPhrase = "Found"; break;
  222. case 303: statusPhrase = "See Other"; break;
  223. case 304: statusPhrase = "Not Modified"; break;
  224. case 305: statusPhrase = "Use Proxy"; break;
  225. case 306: statusPhrase = "Switch Proxy"; break;
  226. case 307: statusPhrase = "Temporary Redirect"; break;
  227. case 308: statusPhrase = "Permanent Redirect"; break;
  228. case 400: statusPhrase = "Bad Request"; break;
  229. case 401: statusPhrase = "Unauthorized"; break;
  230. case 402: statusPhrase = "Payment Required"; break;
  231. case 403: statusPhrase = "Forbidden"; break;
  232. case 404: statusPhrase = "Not Found"; break;
  233. case 405: statusPhrase = "Method Not Allowed"; break;
  234. case 406: statusPhrase = "Not Acceptable"; break;
  235. case 407: statusPhrase = "Proxy Authentication Required"; break;
  236. case 408: statusPhrase = "Request Timeout"; break;
  237. case 409: statusPhrase = "Conflict"; break;
  238. case 410: statusPhrase = "Gone"; break;
  239. case 411: statusPhrase = "Length Required"; break;
  240. case 412: statusPhrase = "Precondition Failed"; break;
  241. case 413: statusPhrase = "Payload Too Large"; break;
  242. case 414: statusPhrase = "URI Too Long"; break;
  243. case 415: statusPhrase = "Unsupported Media Type"; break;
  244. case 416: statusPhrase = "Range Not Satisfiable"; break;
  245. case 417: statusPhrase = "Expectation Failed"; break;
  246. case 421: statusPhrase = "Misdirected Request"; break;
  247. case 422: statusPhrase = "Unprocessable Entity"; break;
  248. case 423: statusPhrase = "Locked"; break;
  249. case 424: statusPhrase = "Failed Dependency"; break;
  250. case 425: statusPhrase = "Too Early"; break;
  251. case 426: statusPhrase = "Upgrade Required"; break;
  252. case 427: statusPhrase = "Unassigned"; break;
  253. case 428: statusPhrase = "Precondition Required"; break;
  254. case 429: statusPhrase = "Too Many Requests"; break;
  255. case 431: statusPhrase = "Request Header Fields Too Large"; break;
  256. case 451: statusPhrase = "Unavailable For Legal Reasons"; break;
  257. case 500: statusPhrase = "Internal Server Error"; break;
  258. case 501: statusPhrase = "Not Implemented"; break;
  259. case 502: statusPhrase = "Bad Gateway"; break;
  260. case 503: statusPhrase = "Service Unavailable"; break;
  261. case 504: statusPhrase = "Gateway Timeout"; break;
  262. case 505: statusPhrase = "HTTP Version Not Supported"; break;
  263. case 506: statusPhrase = "Variant Also Negotiates"; break;
  264. case 507: statusPhrase = "Insufficient Storage"; break;
  265. case 508: statusPhrase = "Loop Detected"; break;
  266. case 510: statusPhrase = "Not Extended"; break;
  267. case 511: statusPhrase = "Network Authentication Required"; break;
  268. default: statusPhrase = "Unknown"; break;
  269. }
  270. SetBegin(status, statusPhrase, protocol);
  271. return this;
  272. }
  273. /// <summary>
  274. /// Set the HTTP response begin with a given status, status phrase and protocol
  275. /// </summary>
  276. /// <param name="status">HTTP status</param>
  277. /// <param name="statusPhrase"> HTTP status phrase</param>
  278. /// <param name="protocol">Protocol version</param>
  279. public HttpResponse SetBegin(int status, string statusPhrase, string protocol)
  280. {
  281. // Clear the HTTP response cache
  282. Clear();
  283. // Append the HTTP response protocol version
  284. _cache.Append(protocol);
  285. _protocol = protocol;
  286. _cache.Append(" ");
  287. // Append the HTTP response status
  288. _cache.Append(status.ToString());
  289. Status = status;
  290. _cache.Append(" ");
  291. // Append the HTTP response status phrase
  292. _cache.Append(statusPhrase);
  293. _statusPhrase = statusPhrase;
  294. _cache.Append("\r\n");
  295. return this;
  296. }
  297. /// <summary>
  298. /// Set the HTTP response content type
  299. /// </summary>
  300. /// <param name="extension">Content extension</param>
  301. public HttpResponse SetContentType(string extension)
  302. {
  303. // Try to lookup the content type in mime table
  304. if (_mimeTable.TryGetValue(extension, out string mime))
  305. return SetHeader("Content-Type", mime);
  306. return this;
  307. }
  308. /// <summary>
  309. /// Set the HTTP response header
  310. /// </summary>
  311. /// <param name="key">Header key</param>
  312. /// <param name="value">Header value</param>
  313. public HttpResponse SetHeader(string key, string value)
  314. {
  315. // Append the HTTP response header's key
  316. _cache.Append(key);
  317. _cache.Append(": ");
  318. // Append the HTTP response header's value
  319. _cache.Append(value);
  320. _cache.Append("\r\n");
  321. // Add the header to the corresponding collection
  322. _headers.Add((key, value));
  323. return this;
  324. }
  325. /// <summary>
  326. /// Set the HTTP response cookie
  327. /// </summary>
  328. /// <param name="name">Cookie name</param>
  329. /// <param name="value">Cookie value</param>
  330. /// <param name="maxAge">Cookie age in seconds until it expires (default is 86400)</param>
  331. /// <param name="path">Cookie path (default is "")</param>
  332. /// <param name="domain">Cookie domain (default is "")</param>
  333. /// <param name="secure">Cookie secure flag (default is true)</param>
  334. /// <param name="strict">Cookie strict flag (default is true)</param>
  335. /// <param name="httpOnly">Cookie HTTP-only flag (default is true)</param>
  336. public HttpResponse SetCookie(string name, string value, int maxAge = 86400, string path = "", string domain = "", bool secure = true, bool strict = true, bool httpOnly = true)
  337. {
  338. string key = "Set-Cookie";
  339. // Append the HTTP response header's key
  340. _cache.Append(key);
  341. _cache.Append(": ");
  342. // Append the HTTP response header's value
  343. int valueIndex = (int)_cache.Size;
  344. // Append cookie
  345. _cache.Append(name);
  346. _cache.Append("=");
  347. _cache.Append(value);
  348. _cache.Append("; Max-Age=");
  349. _cache.Append(maxAge.ToString());
  350. if (!string.IsNullOrEmpty(domain))
  351. {
  352. _cache.Append("; Domain=");
  353. _cache.Append(domain);
  354. }
  355. if (!string.IsNullOrEmpty(path))
  356. {
  357. _cache.Append("; Path=");
  358. _cache.Append(path);
  359. }
  360. if (secure)
  361. _cache.Append("; Secure");
  362. if (strict)
  363. _cache.Append("; SameSite=Strict");
  364. if (httpOnly)
  365. _cache.Append("; HttpOnly");
  366. int valueSize = (int)_cache.Size - valueIndex;
  367. string cookie = _cache.ExtractString(valueIndex, valueSize);
  368. _cache.Append("\r\n");
  369. // Add the header to the corresponding collection
  370. _headers.Add((key, cookie));
  371. return this;
  372. }
  373. /// <summary>
  374. /// Set the HTTP response body
  375. /// </summary>
  376. /// <param name="body">Body string content (default is "")</param>
  377. public HttpResponse SetBody(string body = "") => SetBody(body.AsSpan());
  378. /// <summary>
  379. /// Set the HTTP response body
  380. /// </summary>
  381. /// <param name="body">Body string content as a span of characters</param>
  382. public HttpResponse SetBody(ReadOnlySpan<char> body)
  383. {
  384. int length = body.IsEmpty ? 0 : Encoding.UTF8.GetByteCount(body);
  385. // Append content length header
  386. SetHeader("Content-Length", length.ToString());
  387. _cache.Append("\r\n");
  388. int index = (int)_cache.Size;
  389. // Append the HTTP response body
  390. _cache.Append(body);
  391. _bodyIndex = index;
  392. _bodySize = length;
  393. _bodyLength = length;
  394. _bodyLengthProvided = true;
  395. return this;
  396. }
  397. /// <summary>
  398. /// Set the HTTP response body
  399. /// </summary>
  400. /// <param name="body">Body binary content</param>
  401. public HttpResponse SetBody(byte[] body) => SetBody(body.AsSpan());
  402. /// <summary>
  403. /// Set the HTTP response body
  404. /// </summary>
  405. /// <param name="body">Body binary content as a span of bytes</param>
  406. public HttpResponse SetBody(ReadOnlySpan<byte> body)
  407. {
  408. // Append content length header
  409. SetHeader("Content-Length", body.Length.ToString());
  410. _cache.Append("\r\n");
  411. int index = (int)_cache.Size;
  412. // Append the HTTP response body
  413. _cache.Append(body);
  414. _bodyIndex = index;
  415. _bodySize = body.Length;
  416. _bodyLength = body.Length;
  417. _bodyLengthProvided = true;
  418. return this;
  419. }
  420. /// <summary>
  421. /// Set the HTTP response body length
  422. /// </summary>
  423. /// <param name="length">Body length</param>
  424. public HttpResponse SetBodyLength(int length)
  425. {
  426. // Append content length header
  427. SetHeader("Content-Length", length.ToString());
  428. _cache.Append("\r\n");
  429. int index = (int)_cache.Size;
  430. // Clear the HTTP response body
  431. _bodyIndex = index;
  432. _bodySize = 0;
  433. _bodyLength = length;
  434. _bodyLengthProvided = true;
  435. return this;
  436. }
  437. /// <summary>
  438. /// Make OK response
  439. /// </summary>
  440. /// <param name="status">OK status (default is 200 (OK))</param>
  441. public HttpResponse MakeOkResponse(int status = 200)
  442. {
  443. Clear();
  444. SetBegin(status);
  445. SetBody();
  446. return this;
  447. }
  448. /// <summary>
  449. /// Make ERROR response
  450. /// </summary>
  451. /// <param name="content">Error content (default is "")</param>
  452. /// <param name="contentType">Error content type (default is "text/plain; charset=UTF-8")</param>
  453. public HttpResponse MakeErrorResponse(string content = "", string contentType = "text/plain; charset=UTF-8")
  454. {
  455. return MakeErrorResponse(500, content, contentType);
  456. }
  457. /// <summary>
  458. /// Make ERROR response
  459. /// </summary>
  460. /// <param name="status">Error status</param>
  461. /// <param name="content">Error content (default is "")</param>
  462. /// <param name="contentType">Error content type (default is "text/plain; charset=UTF-8")</param>
  463. public HttpResponse MakeErrorResponse(int status, string content = "", string contentType = "text/plain; charset=UTF-8")
  464. {
  465. Clear();
  466. SetBegin(status);
  467. if (!string.IsNullOrEmpty(contentType))
  468. SetHeader("Content-Type", contentType);
  469. SetBody(content);
  470. return this;
  471. }
  472. /// <summary>
  473. /// Make HEAD response
  474. /// </summary>
  475. public HttpResponse MakeHeadResponse()
  476. {
  477. Clear();
  478. SetBegin(200);
  479. SetBody();
  480. return this;
  481. }
  482. /// <summary>
  483. /// Make GET response
  484. /// </summary>
  485. /// <param name="content">String content (default is "")</param>
  486. /// <param name="contentType">Content type (default is "text/plain; charset=UTF-8")</param>
  487. public HttpResponse MakeGetResponse(string content = "", string contentType = "text/plain; charset=UTF-8") => MakeGetResponse(content.AsSpan(), contentType);
  488. /// <summary>
  489. /// Make GET response
  490. /// </summary>
  491. /// <param name="content">String content as a span of characters</param>
  492. /// <param name="contentType">Content type (default is "text/plain; charset=UTF-8")</param>
  493. public HttpResponse MakeGetResponse(ReadOnlySpan<char> content, string contentType = "text/plain; charset=UTF-8")
  494. {
  495. Clear();
  496. SetBegin(200);
  497. if (!string.IsNullOrEmpty(contentType))
  498. SetHeader("Content-Type", contentType);
  499. SetBody(content);
  500. return this;
  501. }
  502. /// <summary>
  503. /// Make GET response
  504. /// </summary>
  505. /// <param name="content">Binary content</param>
  506. /// <param name="contentType">Content type (default is "")</param>
  507. public HttpResponse MakeGetResponse(byte[] content, string contentType = "") => MakeGetResponse(content.AsSpan(), contentType);
  508. /// <summary>
  509. /// Make GET response
  510. /// </summary>
  511. /// <param name="content">Binary content as a span of bytes</param>
  512. /// <param name="contentType">Content type (default is "")</param>
  513. public HttpResponse MakeGetResponse(ReadOnlySpan<byte> content, string contentType = "")
  514. {
  515. Clear();
  516. SetBegin(200);
  517. if (!string.IsNullOrEmpty(contentType))
  518. SetHeader("Content-Type", contentType);
  519. SetBody(content);
  520. return this;
  521. }
  522. /// <summary>
  523. /// Make OPTIONS response
  524. /// </summary>
  525. /// <param name="allow">Allow methods (default is "HEAD,GET,POST,PUT,DELETE,OPTIONS,TRACE")</param>
  526. public HttpResponse MakeOptionsResponse(string allow = "HEAD,GET,POST,PUT,DELETE,OPTIONS,TRACE")
  527. {
  528. Clear();
  529. SetBegin(200);
  530. SetHeader("Allow", allow);
  531. SetBody();
  532. return this;
  533. }
  534. /// <summary>
  535. /// Make TRACE response
  536. /// </summary>
  537. /// <param name="content">String content</param>
  538. public HttpResponse MakeTraceResponse(string content) => MakeTraceResponse(content.AsSpan());
  539. /// <summary>
  540. /// Make TRACE response
  541. /// </summary>
  542. /// <param name="content">String content as a span of characters</param>
  543. public HttpResponse MakeTraceResponse(ReadOnlySpan<char> content)
  544. {
  545. Clear();
  546. SetBegin(200);
  547. SetHeader("Content-Type", "message/http");
  548. SetBody(content);
  549. return this;
  550. }
  551. /// <summary>
  552. /// Make TRACE response
  553. /// </summary>
  554. /// <param name="content">Binary content</param>
  555. public HttpResponse MakeTraceResponse(byte[] content) => MakeTraceResponse(content.AsSpan());
  556. /// <summary>
  557. /// Make TRACE response
  558. /// </summary>
  559. /// <param name="content">Binary content as a span of bytes</param>
  560. public HttpResponse MakeTraceResponse(ReadOnlySpan<byte> content)
  561. {
  562. Clear();
  563. SetBegin(200);
  564. SetHeader("Content-Type", "message/http");
  565. SetBody(content);
  566. return this;
  567. }
  568. /// <summary>
  569. /// Make TRACE response
  570. /// </summary>
  571. /// <param name="request">HTTP request</param>
  572. public HttpResponse MakeTraceResponse(HttpRequest request) => MakeTraceResponse(request.Cache.AsSpan());
  573. // HTTP response status phrase
  574. private string _statusPhrase;
  575. // HTTP response protocol
  576. private string _protocol;
  577. // HTTP response headers
  578. private List<(string, string)> _headers = new List<(string, string)>();
  579. // HTTP response body
  580. private int _bodyIndex;
  581. private int _bodySize;
  582. private int _bodyLength;
  583. private bool _bodyLengthProvided;
  584. // HTTP response cache
  585. private Buffer _cache = new Buffer();
  586. private int _cacheSize;
  587. // HTTP response mime table
  588. private static readonly Dictionary<string, string> _mimeTable;
  589. // Is pending parts of HTTP response
  590. internal bool IsPendingHeader()
  591. {
  592. return (!IsErrorSet && (_bodyIndex == 0));
  593. }
  594. internal bool IsPendingBody()
  595. {
  596. return (!IsErrorSet && (_bodyIndex > 0) && (_bodySize > 0));
  597. }
  598. // Receive parts of HTTP response
  599. internal bool ReceiveHeader(byte[] buffer, int offset, int size)
  600. {
  601. // Update the request cache
  602. _cache.Append(buffer, offset, size);
  603. // Try to seek for HTTP header separator
  604. for (int i = _cacheSize; i < (int)_cache.Size; i++)
  605. {
  606. // Check for the request cache out of bounds
  607. if ((i + 3) >= (int)_cache.Size)
  608. break;
  609. // Check for the header separator
  610. if ((_cache[i + 0] == '\r') && (_cache[i + 1] == '\n') && (_cache[i + 2] == '\r') && (_cache[i + 3] == '\n'))
  611. {
  612. int index = 0;
  613. // Set the error flag for a while...
  614. IsErrorSet = true;
  615. // Parse protocol version
  616. int protocolIndex = index;
  617. int protocolSize = 0;
  618. while (_cache[index] != ' ')
  619. {
  620. protocolSize++;
  621. index++;
  622. if (index >= (int)_cache.Size)
  623. return false;
  624. }
  625. index++;
  626. if ((index >= (int)_cache.Size))
  627. return false;
  628. _protocol = _cache.ExtractString(protocolIndex, protocolSize);
  629. // Parse status code
  630. int statusIndex = index;
  631. int statusSize = 0;
  632. while (_cache[index] != ' ')
  633. {
  634. if ((_cache[index] < '0') || (_cache[index] > '9'))
  635. return false;
  636. statusSize++;
  637. index++;
  638. if (index >= (int)_cache.Size)
  639. return false;
  640. }
  641. Status = 0;
  642. for (int j = statusIndex; j < (statusIndex + statusSize); j++)
  643. {
  644. Status *= 10;
  645. Status += _cache[j] - '0';
  646. }
  647. index++;
  648. if (index >= (int)_cache.Size)
  649. return false;
  650. // Parse status phrase
  651. int statusPhraseIndex = index;
  652. int statusPhraseSize = 0;
  653. while (_cache[index] != '\r')
  654. {
  655. statusPhraseSize++;
  656. index++;
  657. if (index >= (int)_cache.Size)
  658. return false;
  659. }
  660. index++;
  661. if ((index >= (int)_cache.Size) || (_cache[index] != '\n'))
  662. return false;
  663. index++;
  664. if (index >= (int)_cache.Size)
  665. return false;
  666. _statusPhrase = _cache.ExtractString(statusPhraseIndex, statusPhraseSize);
  667. // Parse headers
  668. while ((index < (int)_cache.Size) && (index < i))
  669. {
  670. // Parse header name
  671. int headerNameIndex = index;
  672. int headerNameSize = 0;
  673. while (_cache[index] != ':')
  674. {
  675. headerNameSize++;
  676. index++;
  677. if (index >= i)
  678. break;
  679. if (index >= (int)_cache.Size)
  680. return false;
  681. }
  682. index++;
  683. if (index >= i)
  684. break;
  685. if (index >= (int)_cache.Size)
  686. return false;
  687. // Skip all prefix space characters
  688. while (char.IsWhiteSpace((char)_cache[index]))
  689. {
  690. index++;
  691. if (index >= i)
  692. break;
  693. if (index >= (int)_cache.Size)
  694. return false;
  695. }
  696. // Parse header value
  697. int headerValueIndex = index;
  698. int headerValueSize = 0;
  699. while (_cache[index] != '\r')
  700. {
  701. headerValueSize++;
  702. index++;
  703. if (index >= i)
  704. break;
  705. if (index >= (int)_cache.Size)
  706. return false;
  707. }
  708. index++;
  709. if ((index >= (int)_cache.Size) || (_cache[index] != '\n'))
  710. return false;
  711. index++;
  712. if (index >= (int)_cache.Size)
  713. return false;
  714. // Validate header name and value (sometimes value can be empty)
  715. if (headerNameSize == 0)
  716. return false;
  717. // Add a new header
  718. string headerName = _cache.ExtractString(headerNameIndex, headerNameSize);
  719. string headerValue = _cache.ExtractString(headerValueIndex, headerValueSize);
  720. _headers.Add((headerName, headerValue));
  721. // Try to find the body content length
  722. if (string.Compare(headerName, "Content-Length", StringComparison.OrdinalIgnoreCase) == 0)
  723. {
  724. _bodyLength = 0;
  725. for (int j = headerValueIndex; j < (headerValueIndex + headerValueSize); j++)
  726. {
  727. if ((_cache[j] < '0') || (_cache[j] > '9'))
  728. return false;
  729. _bodyLength *= 10;
  730. _bodyLength += _cache[j] - '0';
  731. _bodyLengthProvided = true;
  732. }
  733. }
  734. }
  735. // Reset the error flag
  736. IsErrorSet = false;
  737. // Update the body index and size
  738. _bodyIndex = i + 4;
  739. _bodySize = (int)_cache.Size - i - 4;
  740. // Update the parsed cache size
  741. _cacheSize = (int)_cache.Size;
  742. return true;
  743. }
  744. }
  745. // Update the parsed cache size
  746. _cacheSize = ((int)_cache.Size >= 3) ? ((int)_cache.Size - 3) : 0;
  747. return false;
  748. }
  749. internal bool ReceiveBody(byte[] buffer, int offset, int size)
  750. {
  751. // Update the request cache
  752. _cache.Append(buffer, offset, size);
  753. // Update the parsed cache size
  754. _cacheSize = (int)_cache.Size;
  755. // Update body size
  756. _bodySize += size;
  757. // Check if the body length was provided
  758. if (_bodyLengthProvided)
  759. {
  760. // Was the body fully received?
  761. if (_bodySize >= _bodyLength)
  762. {
  763. _bodySize = _bodyLength;
  764. return true;
  765. }
  766. }
  767. else
  768. {
  769. // Check the body content to find the response body end
  770. if (_bodySize >= 4)
  771. {
  772. int index = _bodyIndex + _bodySize - 4;
  773. // Was the body fully received?
  774. if ((_cache[index + 0] == '\r') && (_cache[index + 1] == '\n') && (_cache[index + 2] == '\r') &&
  775. (_cache[index + 3] == '\n'))
  776. {
  777. _bodyLength = _bodySize;
  778. return true;
  779. }
  780. }
  781. }
  782. // Body was received partially...
  783. return false;
  784. }
  785. }
  786. }