/// <summary>Reads an instance using the specified reader.</summary>
        /// <param name="server">The server.</param>
        /// <param name="firstLine">The first line.</param>
        /// <param name="client">The client.</param>
        /// <returns></returns>
        /// <exception cref="WebServerException">
        /// Malformed request {0}!
        /// or
        /// Command {0} is not supported!
        /// or
        /// Protocol {0} is not supported!.
        /// </exception>
        internal static WebRequest Load(WebServer server, string firstLine, WebServerClient client)
        {
            var req = new WebRequest(server)
            {
                SourceAddress = client.RemoteEndPoint.Address.ToString(),
                LocalPort     = client.LocalEndPoint.Port,
                FirstLine     = firstLine,
            };

            if (server.VerboseMode)
            {
                Trace.TraceInformation($"Request {req.ID} {firstLine}");
            }

            string[] parts = req.FirstLine.Split(' ');
            if (parts.Length != 3)
            {
                throw new WebServerException(WebError.InvalidOperation, "Malformed request {0}!", req.FirstLine);
            }

            req.Command = parts[0].Parse <WebCommand>();
            switch (req.Command)
            {
            case WebCommand.DELETE:
            case WebCommand.GET:
            case WebCommand.OPTIONS:
            case WebCommand.POST:
            case WebCommand.PUT: break;

            default: throw new WebServerException(WebError.InvalidOperation, "Command {0} is not supported!", parts[0]);
            }
            switch (req.Protocol = parts[2])
            {
            case "HTTP/1.0":
            case "HTTP/1.1": break;

            default: throw new WebServerException(WebError.InvalidOperation, "Protocol {0} is not supported!", parts[2]);
            }
            var headers = new Dictionary <string, string>();

            while (true)
            {
                string line = client.Reader.ReadLine();
                if (server.VerboseMode)
                {
                    Trace.TraceInformation($"Request {req.ID} {line}");
                }

                if (string.IsNullOrEmpty(line))
                {
                    break;
                }

                string[] header = line.Split(new char[] { ':' }, 2);
                headers.Add(header[0].ToLower().Trim(), header[1].Trim());
            }
            req.Headers = new ReadOnlyDictionary <string, string>(headers);
            req.DecodeUrl(parts[1]);
            return(req);
        }
        void HandleRequest(WebServerClient client, WebData data)
        {
            // add acl headers
            if (Certificate != null)
            {
                data.Result.Headers["Strict-Transport-Security"] = "max-age=604800; includeSubDomains";
            }

            data.Result.Headers["Access-Control-Allow-Headers"] = "Session";
            if (data.Method?.PageAttribute?.AuthType == WebServerAuthType.Basic)
            {
                data.Result.Headers["Access-Control-Allow-Credentials"] = "true";
                data.Result.Headers["Access-Control-Allow-Headers"]    += ", Authorization";
            }
            if (!data.Result.Headers.ContainsKey("Access-Control-Allow-Origin"))
            {
                data.Result.Headers["Access-Control-Allow-Origin"] = string.IsNullOrEmpty(data.Request.Origin) ? "*" : data.Request.Origin;
            }

            if (!data.Result.Headers.ContainsKey("Access-Control-Allow-Methods"))
            {
                data.Result.Headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS";
            }

            if (data.Method?.PageAttribute?.AllowHeaders != null)
            {
                data.Result.Headers["Access-Control-Allow-Headers"] += ", " + data.Method.PageAttribute.AllowHeaders;
            }

            if (data.Request.Command == WebCommand.OPTIONS)
            {
                data.Result.AddMessage(data.Method, "Options transfered successfully.");
                client.SendAnswer(data);
                return;
            }

            data.Request.LoadPost(client);

            if (data.Method == null)
            {
                Trace.TraceInformation("Static Request: {0}", data.Request);

                if (StaticRequest != null)
                {
                    var e = new WebPageEventArgs(data);
                    StaticRequest(this, e);
                    if (e.Handled)
                    {
                        client.SendAnswer(data);
                        return;
                    }
                }

                if (EnableTemplates && RunTemplate(data))
                {
                    Trace.TraceInformation("Template: {0} {1}", data.Request, data.Result);
                    client.SendAnswer(data);
                    return;
                }

                // no method - send static file ?
                WebAnswer staticFile = GetStaticFile(data.Request);
                if (staticFile != null)
                {
                    // file present, send answer
                    Trace.TraceInformation("Static file: {0} {1}", data.Request, staticFile);
                    SetStaticCacheTime(staticFile, StaticPathCacheTime);
                    client.SendAnswer(staticFile);
                    return;
                }

                // static path access -> set cache time
                SetStaticCacheTime(data, StaticPathCacheTime);

                // file not present, check special functions
                if (EnableExplain && (data.Request.DecodedUrl.ToLower() == "/explain" || data.Request.DecodedUrl.ToLower() == "/functionlist"))
                {
                    // special page (function list / explain)
                    explain.Explain(data);
                }
                else if (EnableFileListing)
                {
                    // list files
                    GetStaticFileListing(data);
                }
                else
                {
                    // no static -> error
                    data.Result.AddMessage(data.Request.PlainUrl, WebError.NotFound, $"The requested URL {data.Request.DecodedUrl} was not found on this server.");
                }
                client.SendAnswer(data);
                return;
            }

            // invoke method
            CallMethod(data);

            // send answer
            client.SendAnswer(data);
        }
        /// <summary>Handles a client stage1 (preparations).</summary>
        /// <remarks>Performs the firewall checks and enters stage2.</remarks>
        internal void HandleClient(WebServerClient client)
        {
            System.Globalization.CultureInfo threadCulture = Thread.CurrentThread.CurrentCulture;
            int threadId            = Thread.CurrentThread.ManagedThreadId;
            WebResultBuilder result = null;

            try
            {
                // callback for connected client
                ClientConnected?.Invoke(this, new WebClientEventArgs(client));

                // do request handling
                int requestNumber = 0;
                if (PerformanceChecks)
                {
                    Trace.TraceInformation(
                        $"HandleClient [{threadId}] <cyan>{client.RemoteEndPoint}<default> ready to receive request. " +
                        $"Elapsed <cyan>{client.StopWatch.Elapsed.FormatTime()}<default>.");
                }
                while (client.IsConnected)
                {
                    result = null;
                    if (PerformanceChecks && requestNumber > 0)
                    {
                        Trace.TraceInformation(
                            $"HandleClient [{threadId}] <cyan>{client.RemoteEndPoint}<default> request <green>{requestNumber}<default> handling completed. " +
                            $"Elapsed <cyan>{client.StopWatch.Elapsed.FormatTime()}<default>.");
                    }

                    // read first request line
                    string firstLine = client.Reader.ReadLine();
                    client.StopWatch.Reset();
                    if (PerformanceChecks)
                    {
                        Trace.TraceInformation(
                            $"HandleClient [{threadId}] <cyan>{client.RemoteEndPoint}<default> start handling request <cyan>{++requestNumber}<default>. " +
                            $"Elapsed <cyan>{client.StopWatch.Elapsed.FormatTime()}<default>.");
                    }

                    // load request
                    var request = WebRequest.Load(this, firstLine, client);

                    // prepare web data object
                    var data = new WebData(request, client.StopWatch);
                    result = data.Result;

                    // update thread culture
                    Thread.CurrentThread.CurrentCulture = data.Request.Culture;

                    // handle request but change some default exceptions to web exceptions
                    try { HandleRequest(client, data); }
                    catch (ObjectDisposedException)
                    {
                        Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed");
                    }
                    catch (InvalidOperationException ex) { throw new WebServerException(ex, WebError.InvalidOperation, 0, ex.Message); }
                    catch (ArgumentException ex) { throw new WebServerException(ex, WebError.InvalidParameters, 0, ex.Message); }
                }
            }
            catch (WebServerException ex)
            {
                Trace.TraceInformation(ex.ToString());
                if (result == null)
                {
                    result = new WebResultBuilder(this);
                }

                result.AddMessage(WebMessage.Create(ex));
                if (ex.Error == WebError.AuthenticationRequired || ex.Error == WebError.InvalidTransactionKey)
                {
                    result.Headers["WWW-Authenticate"] = $"Basic realm=\"{AssemblyVersionInfo.Program.Company} - {AssemblyVersionInfo.Program.Product}\"";
                }
                result.CloseAfterAnswer = true;
                client.SendAnswer(result.ToAnswer());
            }
            catch (SocketException)
            {
                Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed");
                /*client closed connection*/
            }
            catch (EndOfStreamException)
            {
                /*client closed connection*/
                Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed");
            }
            catch (Exception ex)
            {
                if (ex.InnerException is SocketException)
                {
                    Trace.TraceInformation($"HandleClient [{threadId}] <red>{client.RemoteEndPoint}<default> Connection closed");
                    return;
                }

                string supportCode = Base32.Safe.Encode(Environment.TickCount);
                Trace.TraceError("<red>Unhandled Internal Server Error<default> Code {1}\n{0}", ex.ToString(), supportCode);

                if (result == null)
                {
                    result = new WebResultBuilder(this);
                }

                result.AddMessage(ex.Source, WebError.InternalServerError, $"Internal Server Error\nUnexpected result on request.\nPlease contact support!\nSupport Code = {supportCode}");
                result.CloseAfterAnswer = true;
                client.SendAnswer(result.ToAnswer());
            }
            finally
            {
                while (client.IsConnected && client.Reader.Available == 0)
                {
                    Thread.Sleep(1);
                }

                client.Close();
                if (client != null)
                {
                    ClientDisconnected?.Invoke(this, new WebClientEventArgs(client));
                }

                // reset thread culture
                if (Thread.CurrentThread.CurrentCulture != threadCulture)
                {
                    Thread.CurrentThread.CurrentCulture = threadCulture;
                }
            }
        }
        internal void LoadPost(WebServerClient client)
        {
            if (!Headers.TryGetValue("content-type", out string contentType))
            {
                return;
            }

            string contentTypeShort = contentType.BeforeFirst(';').Trim().ToLower();

            switch (contentTypeShort)
            {
            case "application/x-www-form-urlencoded": break;

            case "application/octet-stream": break;

            case "multipart/form-data": break;

            default: throw new WebServerException(WebError.UnknownContent, 0, "Unknown content type!");
            }
            int size = 0;

            {
                if (Headers.TryGetValue("content-length", out string sizeStr))
                {
                    int.TryParse(sizeStr, out size);
                }
            }

            // if (size > 20 * 1024 * 1024) throw new CaveWebException(CaveWebError.MaximumSizeExceeded, "Maximum transfer size exceeded!");
            if (Headers.ContainsKey("expect"))
            {
                if (Headers["expect"].Contains("100-continue"))
                {
                    string @continue = $"{Protocol} {(int)HttpStatusCode.Continue} {HttpStatusCode.Continue}";
                    if (Server.VerboseMode)
                    {
                        Trace.TraceInformation($"Request {ID} {@continue}");
                    }

                    client.Writer.WriteLine(@continue);
                    client.Writer.WriteLine();
                }
            }
            byte[] data = null;
            if (Headers.TryGetValue("transfer-encoding", out string transferEncoding))
            {
                switch (transferEncoding.ToLower().Trim())
                {
                case "chunked":
                    var buf = new FifoBuffer();
                    while (true)
                    {
                        string line      = client.Reader.ReadLine();
                        int    chunkSize = Convert.ToInt32(line, 16);
                        if (chunkSize == 0)
                        {
                            break;
                        }

                        byte[] chunkData = client.Reader.ReadBytes(chunkSize);
                        buf.Enqueue(chunkData);
                        client.Reader.ReadLine();
                    }
                    data = buf.ToArray();
                    break;

                default:
                    throw new WebServerException(WebError.UnknownContent, 0, string.Format("Unknown transfer encoding {0}", transferEncoding));
                }
            }
            switch (contentTypeShort)
            {
            case "application/x-www-form-urlencoded":
                if (data != null)
                {
                    DecodeUrl(Encoding.ASCII.GetString(data).Replace('+', ' '), true);
                }
                else
                {
                    DecodeUrl(Encoding.ASCII.GetString(client.Reader.ReadBytes(size)).Replace('+', ' '), true);
                }
                break;

            case "application/octet-stream":
                if (data != null)
                {
                    SetPostData(data);
                }
                else
                {
                    SetPostData(client.Reader.ReadBytes(size));
                }
                break;

            case "multipart/form-data":
                if (data != null)
                {
                    DecodeMultiPartFormData(contentType, new DataReader(new MemoryStream(data), newLineMode: NewLineMode.CRLF));
                }
                else
                {
                    DecodeMultiPartFormData(contentType, client.Reader);
                }
                break;

            default: throw new WebServerException(WebError.UnknownContent, 0, "Unknown content type!");
            }
        }