123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Text;
- namespace NetCoreServer
- {
- /// <summary>
- /// HTTP response is used to create or process parameters of HTTP protocol response(status, headers, etc).
- /// </summary>
- /// <remarks>Not thread-safe.</remarks>
- public class HttpResponse
- {
- static HttpResponse()
- {
- _mimeTable = new Dictionary<string, string>
- {
- // Base content types
- { ".html", "text/html" },
- { ".css", "text/css" },
- { ".js", "text/javascript" },
- { ".vue", "text/html" },
- { ".xml", "text/xml" },
- // Application content types
- { ".atom", "application/atom+xml" },
- { ".fastsoap", "application/fastsoap" },
- { ".gzip", "application/gzip" },
- { ".json", "application/json" },
- { ".map", "application/json" },
- { ".pdf", "application/pdf" },
- { ".ps", "application/postscript" },
- { ".soap", "application/soap+xml" },
- { ".sql", "application/sql" },
- { ".xslt", "application/xslt+xml" },
- { ".zip", "application/zip" },
- { ".zlib", "application/zlib" },
- // Audio content types
- { ".aac", "audio/aac" },
- { ".ac3", "audio/ac3" },
- { ".mp3", "audio/mpeg" },
- { ".ogg", "audio/ogg" },
- // Font content types
- { ".ttf", "font/ttf" },
- // Image content types
- { ".bmp", "image/bmp" },
- { ".emf", "image/emf" },
- { ".gif", "image/gif" },
- { ".jpg", "image/jpeg" },
- { ".jpm", "image/jpm" },
- { ".jpx", "image/jpx" },
- { ".jrx", "image/jrx" },
- { ".png", "image/png" },
- { ".svg", "image/svg+xml" },
- { ".tiff", "image/tiff" },
- { ".wmf", "image/wmf" },
- // Message content types
- { ".http", "message/http" },
- { ".s-http", "message/s-http" },
- // Model content types
- { ".mesh", "model/mesh" },
- { ".vrml", "model/vrml" },
- // Text content types
- { ".csv", "text/csv" },
- { ".plain", "text/plain" },
- { ".richtext", "text/richtext" },
- { ".rtf", "text/rtf" },
- { ".rtx", "text/rtx" },
- { ".sgml", "text/sgml" },
- { ".strings", "text/strings" },
- { ".url", "text/uri-list" },
- // Video content types
- { ".H264", "video/H264" },
- { ".H265", "video/H265" },
- { ".mp4", "video/mp4" },
- { ".mpeg", "video/mpeg" },
- { ".raw", "video/raw" }
- };
- }
- /// <summary>
- /// Initialize an empty HTTP response
- /// </summary>
- public HttpResponse()
- {
- Clear();
- }
- /// <summary>
- /// Initialize a new HTTP response with a given status and protocol
- /// </summary>
- /// <param name="status">HTTP status</param>
- /// <param name="protocol">Protocol version (default is "HTTP/1.1")</param>
- public HttpResponse(int status, string protocol = "HTTP/1.1")
- {
- SetBegin(status, protocol);
- }
- /// <summary>
- /// Initialize a new HTTP response with a given status, status phrase and protocol
- /// </summary>
- /// <param name="status">HTTP status</param>
- /// <param name="statusPhrase">HTTP status phrase</param>
- /// <param name="protocol">Protocol version</param>
- public HttpResponse(int status, string statusPhrase, string protocol)
- {
- SetBegin(status, statusPhrase, protocol);
- }
- /// <summary>
- /// Is the HTTP response empty?
- /// </summary>
- public bool IsEmpty { get { return (_cache.Size > 0); } }
- /// <summary>
- /// Is the HTTP response error flag set?
- /// </summary>
- public bool IsErrorSet { get; private set; }
- /// <summary>
- /// Get the HTTP response status
- /// </summary>
- public int Status { get; private set; }
- /// <summary>
- /// Get the HTTP response status phrase
- /// </summary>
- public string StatusPhrase { get { return _statusPhrase; } }
- /// <summary>
- /// Get the HTTP response protocol version
- /// </summary>
- public string Protocol { get { return _protocol; } }
- /// <summary>
- /// Get the HTTP response headers count
- /// </summary>
- public long Headers { get { return _headers.Count; } }
- /// <summary>
- /// Get the HTTP response header by index
- /// </summary>
- public (string, string) Header(int i)
- {
- Debug.Assert((i < _headers.Count), "Index out of bounds!");
- if (i >= _headers.Count)
- return ("", "");
- return _headers[i];
- }
- /// <summary>
- /// Get the HTTP response body as string
- /// </summary>
- public string Body { get { return _cache.ExtractString(_bodyIndex, _bodySize); } }
- /// <summary>
- /// Get the HTTP request body as byte array
- /// </summary>
- public byte[] BodyBytes { get { return _cache.Data[_bodyIndex..(_bodyIndex + _bodySize)]; } }
- /// <summary>
- /// Get the HTTP request body as read-only byte span
- /// </summary>
- public ReadOnlySpan<byte> BodySpan { get { return new ReadOnlySpan<byte>(_cache.Data, _bodyIndex, _bodySize); } }
- /// <summary>
- /// Get the HTTP response body length
- /// </summary>
- public long BodyLength { get { return _bodyLength; } }
- /// <summary>
- /// Get the HTTP response cache content
- /// </summary>
- public Buffer Cache { get { return _cache; } }
- /// <summary>
- /// Get string from the current HTTP response
- /// </summary>
- public override string ToString()
- {
- StringBuilder sb = new StringBuilder();
- sb.AppendLine($"Status: {Status}");
- sb.AppendLine($"Status phrase: {StatusPhrase}");
- sb.AppendLine($"Protocol: {Protocol}");
- sb.AppendLine($"Headers: {Headers}");
- for (int i = 0; i < Headers; i++)
- {
- var header = Header(i);
- sb.AppendLine($"{header.Item1} : {header.Item2}");
- }
- sb.AppendLine($"Body: {BodyLength}");
- sb.AppendLine(Body);
- return sb.ToString();
- }
- /// <summary>
- /// Clear the HTTP response cache
- /// </summary>
- public HttpResponse Clear()
- {
- IsErrorSet = false;
- Status = 0;
- _statusPhrase = "";
- _protocol = "";
- _headers.Clear();
- _bodyIndex = 0;
- _bodySize = 0;
- _bodyLength = 0;
- _bodyLengthProvided = false;
- _cache.Clear();
- _cacheSize = 0;
- return this;
- }
- /// <summary>
- /// Set the HTTP response begin with a given status and protocol
- /// </summary>
- /// <param name="status">HTTP status</param>
- /// <param name="protocol">Protocol version (default is "HTTP/1.1")</param>
- public HttpResponse SetBegin(int status, string protocol = "HTTP/1.1")
- {
- string statusPhrase;
- switch (status)
- {
- case 100: statusPhrase = "Continue"; break;
- case 101: statusPhrase = "Switching Protocols"; break;
- case 102: statusPhrase = "Processing"; break;
- case 103: statusPhrase = "Early Hints"; break;
- case 200: statusPhrase = "OK"; break;
- case 201: statusPhrase = "Created"; break;
- case 202: statusPhrase = "Accepted"; break;
- case 203: statusPhrase = "Non-Authoritative Information"; break;
- case 204: statusPhrase = "No Content"; break;
- case 205: statusPhrase = "Reset Content"; break;
- case 206: statusPhrase = "Partial Content"; break;
- case 207: statusPhrase = "Multi-Status"; break;
- case 208: statusPhrase = "Already Reported"; break;
- case 226: statusPhrase = "IM Used"; break;
- case 300: statusPhrase = "Multiple Choices"; break;
- case 301: statusPhrase = "Moved Permanently"; break;
- case 302: statusPhrase = "Found"; break;
- case 303: statusPhrase = "See Other"; break;
- case 304: statusPhrase = "Not Modified"; break;
- case 305: statusPhrase = "Use Proxy"; break;
- case 306: statusPhrase = "Switch Proxy"; break;
- case 307: statusPhrase = "Temporary Redirect"; break;
- case 308: statusPhrase = "Permanent Redirect"; break;
- case 400: statusPhrase = "Bad Request"; break;
- case 401: statusPhrase = "Unauthorized"; break;
- case 402: statusPhrase = "Payment Required"; break;
- case 403: statusPhrase = "Forbidden"; break;
- case 404: statusPhrase = "Not Found"; break;
- case 405: statusPhrase = "Method Not Allowed"; break;
- case 406: statusPhrase = "Not Acceptable"; break;
- case 407: statusPhrase = "Proxy Authentication Required"; break;
- case 408: statusPhrase = "Request Timeout"; break;
- case 409: statusPhrase = "Conflict"; break;
- case 410: statusPhrase = "Gone"; break;
- case 411: statusPhrase = "Length Required"; break;
- case 412: statusPhrase = "Precondition Failed"; break;
- case 413: statusPhrase = "Payload Too Large"; break;
- case 414: statusPhrase = "URI Too Long"; break;
- case 415: statusPhrase = "Unsupported Media Type"; break;
- case 416: statusPhrase = "Range Not Satisfiable"; break;
- case 417: statusPhrase = "Expectation Failed"; break;
- case 421: statusPhrase = "Misdirected Request"; break;
- case 422: statusPhrase = "Unprocessable Entity"; break;
- case 423: statusPhrase = "Locked"; break;
- case 424: statusPhrase = "Failed Dependency"; break;
- case 425: statusPhrase = "Too Early"; break;
- case 426: statusPhrase = "Upgrade Required"; break;
- case 427: statusPhrase = "Unassigned"; break;
- case 428: statusPhrase = "Precondition Required"; break;
- case 429: statusPhrase = "Too Many Requests"; break;
- case 431: statusPhrase = "Request Header Fields Too Large"; break;
- case 451: statusPhrase = "Unavailable For Legal Reasons"; break;
- case 500: statusPhrase = "Internal Server Error"; break;
- case 501: statusPhrase = "Not Implemented"; break;
- case 502: statusPhrase = "Bad Gateway"; break;
- case 503: statusPhrase = "Service Unavailable"; break;
- case 504: statusPhrase = "Gateway Timeout"; break;
- case 505: statusPhrase = "HTTP Version Not Supported"; break;
- case 506: statusPhrase = "Variant Also Negotiates"; break;
- case 507: statusPhrase = "Insufficient Storage"; break;
- case 508: statusPhrase = "Loop Detected"; break;
- case 510: statusPhrase = "Not Extended"; break;
- case 511: statusPhrase = "Network Authentication Required"; break;
- default: statusPhrase = "Unknown"; break;
- }
- SetBegin(status, statusPhrase, protocol);
- return this;
- }
- /// <summary>
- /// Set the HTTP response begin with a given status, status phrase and protocol
- /// </summary>
- /// <param name="status">HTTP status</param>
- /// <param name="statusPhrase"> HTTP status phrase</param>
- /// <param name="protocol">Protocol version</param>
- public HttpResponse SetBegin(int status, string statusPhrase, string protocol)
- {
- // Clear the HTTP response cache
- Clear();
- // Append the HTTP response protocol version
- _cache.Append(protocol);
- _protocol = protocol;
- _cache.Append(" ");
- // Append the HTTP response status
- _cache.Append(status.ToString());
- Status = status;
- _cache.Append(" ");
- // Append the HTTP response status phrase
- _cache.Append(statusPhrase);
- _statusPhrase = statusPhrase;
- _cache.Append("\r\n");
- return this;
- }
- /// <summary>
- /// Set the HTTP response content type
- /// </summary>
- /// <param name="extension">Content extension</param>
- public HttpResponse SetContentType(string extension)
- {
- // Try to lookup the content type in mime table
- if (_mimeTable.TryGetValue(extension, out string mime))
- return SetHeader("Content-Type", mime);
- return this;
- }
- /// <summary>
- /// Set the HTTP response header
- /// </summary>
- /// <param name="key">Header key</param>
- /// <param name="value">Header value</param>
- public HttpResponse SetHeader(string key, string value)
- {
- // Append the HTTP response header's key
- _cache.Append(key);
- _cache.Append(": ");
- // Append the HTTP response header's value
- _cache.Append(value);
- _cache.Append("\r\n");
- // Add the header to the corresponding collection
- _headers.Add((key, value));
- return this;
- }
- /// <summary>
- /// Set the HTTP response cookie
- /// </summary>
- /// <param name="name">Cookie name</param>
- /// <param name="value">Cookie value</param>
- /// <param name="maxAge">Cookie age in seconds until it expires (default is 86400)</param>
- /// <param name="path">Cookie path (default is "")</param>
- /// <param name="domain">Cookie domain (default is "")</param>
- /// <param name="secure">Cookie secure flag (default is true)</param>
- /// <param name="strict">Cookie strict flag (default is true)</param>
- /// <param name="httpOnly">Cookie HTTP-only flag (default is true)</param>
- public HttpResponse SetCookie(string name, string value, int maxAge = 86400, string path = "", string domain = "", bool secure = true, bool strict = true, bool httpOnly = true)
- {
- string key = "Set-Cookie";
- // Append the HTTP response header's key
- _cache.Append(key);
- _cache.Append(": ");
- // Append the HTTP response header's value
- int valueIndex = (int)_cache.Size;
- // Append cookie
- _cache.Append(name);
- _cache.Append("=");
- _cache.Append(value);
- _cache.Append("; Max-Age=");
- _cache.Append(maxAge.ToString());
- if (!string.IsNullOrEmpty(domain))
- {
- _cache.Append("; Domain=");
- _cache.Append(domain);
- }
- if (!string.IsNullOrEmpty(path))
- {
- _cache.Append("; Path=");
- _cache.Append(path);
- }
- if (secure)
- _cache.Append("; Secure");
- if (strict)
- _cache.Append("; SameSite=Strict");
- if (httpOnly)
- _cache.Append("; HttpOnly");
- int valueSize = (int)_cache.Size - valueIndex;
- string cookie = _cache.ExtractString(valueIndex, valueSize);
- _cache.Append("\r\n");
- // Add the header to the corresponding collection
- _headers.Add((key, cookie));
- return this;
- }
- /// <summary>
- /// Set the HTTP response body
- /// </summary>
- /// <param name="body">Body string content (default is "")</param>
- public HttpResponse SetBody(string body = "") => SetBody(body.AsSpan());
- /// <summary>
- /// Set the HTTP response body
- /// </summary>
- /// <param name="body">Body string content as a span of characters</param>
- public HttpResponse SetBody(ReadOnlySpan<char> body)
- {
- int length = body.IsEmpty ? 0 : Encoding.UTF8.GetByteCount(body);
- // Append content length header
- SetHeader("Content-Length", length.ToString());
- _cache.Append("\r\n");
- int index = (int)_cache.Size;
- // Append the HTTP response body
- _cache.Append(body);
- _bodyIndex = index;
- _bodySize = length;
- _bodyLength = length;
- _bodyLengthProvided = true;
- return this;
- }
- /// <summary>
- /// Set the HTTP response body
- /// </summary>
- /// <param name="body">Body binary content</param>
- public HttpResponse SetBody(byte[] body) => SetBody(body.AsSpan());
- /// <summary>
- /// Set the HTTP response body
- /// </summary>
- /// <param name="body">Body binary content as a span of bytes</param>
- public HttpResponse SetBody(ReadOnlySpan<byte> body)
- {
- // Append content length header
- SetHeader("Content-Length", body.Length.ToString());
- _cache.Append("\r\n");
- int index = (int)_cache.Size;
- // Append the HTTP response body
- _cache.Append(body);
- _bodyIndex = index;
- _bodySize = body.Length;
- _bodyLength = body.Length;
- _bodyLengthProvided = true;
- return this;
- }
- /// <summary>
- /// Set the HTTP response body length
- /// </summary>
- /// <param name="length">Body length</param>
- public HttpResponse SetBodyLength(int length)
- {
- // Append content length header
- SetHeader("Content-Length", length.ToString());
- _cache.Append("\r\n");
- int index = (int)_cache.Size;
- // Clear the HTTP response body
- _bodyIndex = index;
- _bodySize = 0;
- _bodyLength = length;
- _bodyLengthProvided = true;
- return this;
- }
- /// <summary>
- /// Make OK response
- /// </summary>
- /// <param name="status">OK status (default is 200 (OK))</param>
- public HttpResponse MakeOkResponse(int status = 200)
- {
- Clear();
- SetBegin(status);
- SetBody();
- return this;
- }
- /// <summary>
- /// Make ERROR response
- /// </summary>
- /// <param name="content">Error content (default is "")</param>
- /// <param name="contentType">Error content type (default is "text/plain; charset=UTF-8")</param>
- public HttpResponse MakeErrorResponse(string content = "", string contentType = "text/plain; charset=UTF-8")
- {
- return MakeErrorResponse(500, content, contentType);
- }
- /// <summary>
- /// Make ERROR response
- /// </summary>
- /// <param name="status">Error status</param>
- /// <param name="content">Error content (default is "")</param>
- /// <param name="contentType">Error content type (default is "text/plain; charset=UTF-8")</param>
- public HttpResponse MakeErrorResponse(int status, string content = "", string contentType = "text/plain; charset=UTF-8")
- {
- Clear();
- SetBegin(status);
- if (!string.IsNullOrEmpty(contentType))
- SetHeader("Content-Type", contentType);
- SetBody(content);
- return this;
- }
- /// <summary>
- /// Make HEAD response
- /// </summary>
- public HttpResponse MakeHeadResponse()
- {
- Clear();
- SetBegin(200);
- SetBody();
- return this;
- }
- /// <summary>
- /// Make GET response
- /// </summary>
- /// <param name="content">String content (default is "")</param>
- /// <param name="contentType">Content type (default is "text/plain; charset=UTF-8")</param>
- public HttpResponse MakeGetResponse(string content = "", string contentType = "text/plain; charset=UTF-8") => MakeGetResponse(content.AsSpan(), contentType);
- /// <summary>
- /// Make GET response
- /// </summary>
- /// <param name="content">String content as a span of characters</param>
- /// <param name="contentType">Content type (default is "text/plain; charset=UTF-8")</param>
- public HttpResponse MakeGetResponse(ReadOnlySpan<char> content, string contentType = "text/plain; charset=UTF-8")
- {
- Clear();
- SetBegin(200);
- if (!string.IsNullOrEmpty(contentType))
- SetHeader("Content-Type", contentType);
- SetBody(content);
- return this;
- }
- /// <summary>
- /// Make GET response
- /// </summary>
- /// <param name="content">Binary content</param>
- /// <param name="contentType">Content type (default is "")</param>
- public HttpResponse MakeGetResponse(byte[] content, string contentType = "") => MakeGetResponse(content.AsSpan(), contentType);
- /// <summary>
- /// Make GET response
- /// </summary>
- /// <param name="content">Binary content as a span of bytes</param>
- /// <param name="contentType">Content type (default is "")</param>
- public HttpResponse MakeGetResponse(ReadOnlySpan<byte> content, string contentType = "")
- {
- Clear();
- SetBegin(200);
- if (!string.IsNullOrEmpty(contentType))
- SetHeader("Content-Type", contentType);
- SetBody(content);
- return this;
- }
- /// <summary>
- /// Make OPTIONS response
- /// </summary>
- /// <param name="allow">Allow methods (default is "HEAD,GET,POST,PUT,DELETE,OPTIONS,TRACE")</param>
- public HttpResponse MakeOptionsResponse(string allow = "HEAD,GET,POST,PUT,DELETE,OPTIONS,TRACE")
- {
- Clear();
- SetBegin(200);
- SetHeader("Allow", allow);
- SetBody();
- return this;
- }
- /// <summary>
- /// Make TRACE response
- /// </summary>
- /// <param name="content">String content</param>
- public HttpResponse MakeTraceResponse(string content) => MakeTraceResponse(content.AsSpan());
- /// <summary>
- /// Make TRACE response
- /// </summary>
- /// <param name="content">String content as a span of characters</param>
- public HttpResponse MakeTraceResponse(ReadOnlySpan<char> content)
- {
- Clear();
- SetBegin(200);
- SetHeader("Content-Type", "message/http");
- SetBody(content);
- return this;
- }
- /// <summary>
- /// Make TRACE response
- /// </summary>
- /// <param name="content">Binary content</param>
- public HttpResponse MakeTraceResponse(byte[] content) => MakeTraceResponse(content.AsSpan());
- /// <summary>
- /// Make TRACE response
- /// </summary>
- /// <param name="content">Binary content as a span of bytes</param>
- public HttpResponse MakeTraceResponse(ReadOnlySpan<byte> content)
- {
- Clear();
- SetBegin(200);
- SetHeader("Content-Type", "message/http");
- SetBody(content);
- return this;
- }
- /// <summary>
- /// Make TRACE response
- /// </summary>
- /// <param name="request">HTTP request</param>
- public HttpResponse MakeTraceResponse(HttpRequest request) => MakeTraceResponse(request.Cache.AsSpan());
- // HTTP response status phrase
- private string _statusPhrase;
- // HTTP response protocol
- private string _protocol;
- // HTTP response headers
- private List<(string, string)> _headers = new List<(string, string)>();
- // HTTP response body
- private int _bodyIndex;
- private int _bodySize;
- private int _bodyLength;
- private bool _bodyLengthProvided;
- // HTTP response cache
- private Buffer _cache = new Buffer();
- private int _cacheSize;
- // HTTP response mime table
- private static readonly Dictionary<string, string> _mimeTable;
- // Is pending parts of HTTP response
- internal bool IsPendingHeader()
- {
- return (!IsErrorSet && (_bodyIndex == 0));
- }
- internal bool IsPendingBody()
- {
- return (!IsErrorSet && (_bodyIndex > 0) && (_bodySize > 0));
- }
- // Receive parts of HTTP response
- internal bool ReceiveHeader(byte[] buffer, int offset, int size)
- {
- // Update the request cache
- _cache.Append(buffer, offset, size);
- // Try to seek for HTTP header separator
- for (int i = _cacheSize; i < (int)_cache.Size; i++)
- {
- // Check for the request cache out of bounds
- if ((i + 3) >= (int)_cache.Size)
- break;
- // Check for the header separator
- if ((_cache[i + 0] == '\r') && (_cache[i + 1] == '\n') && (_cache[i + 2] == '\r') && (_cache[i + 3] == '\n'))
- {
- int index = 0;
- // Set the error flag for a while...
- IsErrorSet = true;
- // Parse protocol version
- int protocolIndex = index;
- int protocolSize = 0;
- while (_cache[index] != ' ')
- {
- protocolSize++;
- index++;
- if (index >= (int)_cache.Size)
- return false;
- }
- index++;
- if ((index >= (int)_cache.Size))
- return false;
- _protocol = _cache.ExtractString(protocolIndex, protocolSize);
- // Parse status code
- int statusIndex = index;
- int statusSize = 0;
- while (_cache[index] != ' ')
- {
- if ((_cache[index] < '0') || (_cache[index] > '9'))
- return false;
- statusSize++;
- index++;
- if (index >= (int)_cache.Size)
- return false;
- }
- Status = 0;
- for (int j = statusIndex; j < (statusIndex + statusSize); j++)
- {
- Status *= 10;
- Status += _cache[j] - '0';
- }
- index++;
- if (index >= (int)_cache.Size)
- return false;
- // Parse status phrase
- int statusPhraseIndex = index;
- int statusPhraseSize = 0;
- while (_cache[index] != '\r')
- {
- statusPhraseSize++;
- index++;
- if (index >= (int)_cache.Size)
- return false;
- }
- index++;
- if ((index >= (int)_cache.Size) || (_cache[index] != '\n'))
- return false;
- index++;
- if (index >= (int)_cache.Size)
- return false;
- _statusPhrase = _cache.ExtractString(statusPhraseIndex, statusPhraseSize);
- // Parse headers
- while ((index < (int)_cache.Size) && (index < i))
- {
- // Parse header name
- int headerNameIndex = index;
- int headerNameSize = 0;
- while (_cache[index] != ':')
- {
- headerNameSize++;
- index++;
- if (index >= i)
- break;
- if (index >= (int)_cache.Size)
- return false;
- }
- index++;
- if (index >= i)
- break;
- if (index >= (int)_cache.Size)
- return false;
- // Skip all prefix space characters
- while (char.IsWhiteSpace((char)_cache[index]))
- {
- index++;
- if (index >= i)
- break;
- if (index >= (int)_cache.Size)
- return false;
- }
- // Parse header value
- int headerValueIndex = index;
- int headerValueSize = 0;
- while (_cache[index] != '\r')
- {
- headerValueSize++;
- index++;
- if (index >= i)
- break;
- if (index >= (int)_cache.Size)
- return false;
- }
- index++;
- if ((index >= (int)_cache.Size) || (_cache[index] != '\n'))
- return false;
- index++;
- if (index >= (int)_cache.Size)
- return false;
- // Validate header name and value (sometimes value can be empty)
- if (headerNameSize == 0)
- return false;
- // Add a new header
- string headerName = _cache.ExtractString(headerNameIndex, headerNameSize);
- string headerValue = _cache.ExtractString(headerValueIndex, headerValueSize);
- _headers.Add((headerName, headerValue));
- // Try to find the body content length
- if (string.Compare(headerName, "Content-Length", StringComparison.OrdinalIgnoreCase) == 0)
- {
- _bodyLength = 0;
- for (int j = headerValueIndex; j < (headerValueIndex + headerValueSize); j++)
- {
- if ((_cache[j] < '0') || (_cache[j] > '9'))
- return false;
- _bodyLength *= 10;
- _bodyLength += _cache[j] - '0';
- _bodyLengthProvided = true;
- }
- }
- }
- // Reset the error flag
- IsErrorSet = false;
- // Update the body index and size
- _bodyIndex = i + 4;
- _bodySize = (int)_cache.Size - i - 4;
- // Update the parsed cache size
- _cacheSize = (int)_cache.Size;
- return true;
- }
- }
- // Update the parsed cache size
- _cacheSize = ((int)_cache.Size >= 3) ? ((int)_cache.Size - 3) : 0;
- return false;
- }
- internal bool ReceiveBody(byte[] buffer, int offset, int size)
- {
- // Update the request cache
- _cache.Append(buffer, offset, size);
- // Update the parsed cache size
- _cacheSize = (int)_cache.Size;
- // Update body size
- _bodySize += size;
- // Check if the body length was provided
- if (_bodyLengthProvided)
- {
- // Was the body fully received?
- if (_bodySize >= _bodyLength)
- {
- _bodySize = _bodyLength;
- return true;
- }
- }
- else
- {
- // Check the body content to find the response body end
- if (_bodySize >= 4)
- {
- int index = _bodyIndex + _bodySize - 4;
- // Was the body fully received?
- if ((_cache[index + 0] == '\r') && (_cache[index + 1] == '\n') && (_cache[index + 2] == '\r') &&
- (_cache[index + 3] == '\n'))
- {
- _bodyLength = _bodySize;
- return true;
- }
- }
- }
- // Body was received partially...
- return false;
- }
- }
- }
|