/// <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!"); } }