diff --git a/nanoFramework.WebServer/HttpListenerRequestExtensions.cs b/nanoFramework.WebServer/HttpListenerRequestExtensions.cs
index c9363fb..e33decf 100644
--- a/nanoFramework.WebServer/HttpListenerRequestExtensions.cs
+++ b/nanoFramework.WebServer/HttpListenerRequestExtensions.cs
@@ -21,45 +21,82 @@ public static MultipartFormDataParser ReadForm(this HttpListenerRequest httpList
MultipartFormDataParser.Parse(httpListenerRequest.InputStream);
///
- /// Reads a body from the HttpListenerRequest inputstream
+ /// Reads a body from the HttpListenerRequest inputstream.
///
/// The request to read the body from
- /// A byte[] containing the body of the request
+ ///
+ /// A byte[] containing the body of the request, or if the body could not be read.
+ ///
public static byte[] ReadBody(this HttpListenerRequest httpListenerRequest)
{
- byte[] body = new byte[httpListenerRequest.ContentLength64];
- byte[] buffer = new byte[4096];
- Stream stream = httpListenerRequest.InputStream;
+ long contentLength = httpListenerRequest.ContentLength64;
- int position = 0;
+ // check missing or invalid content-length
+ if (contentLength <= 0)
+ {
+ return new byte[0];
+ }
+
+ // Sanity check for huge content-length
+ // A managed array cannot exceed int.MaxValue elements
+ // Treat an oversized Content-Length the same as an allocation failure.
+ if (contentLength > int.MaxValue)
+ {
+ return null;
+ }
- while (true)
+ try
{
- // The stream is (should be) a NetworkStream which might still be receiving data while
- // we're already processing. Give the stream a chance to receive more data or we might
- // end up with "zero bytes read" too soon...
- Thread.Sleep(1);
+ int bodySize = (int)contentLength;
+ byte[] body = new byte[bodySize];
+ byte[] buffer = new byte[4096];
+ Stream stream = httpListenerRequest.InputStream;
- long length = stream.Length;
+ int position = 0;
- if (length > buffer.Length)
+ while (position < bodySize)
{
- length = buffer.Length;
- }
+ // The stream is (should be) a NetworkStream which might still be receiving data while
+ // we're already processing. Give the stream a chance to receive more data or we might
+ // end up with "zero bytes read" too soon...
+ Thread.Sleep(1);
- int bytesRead = stream.Read(buffer, 0, (int)length);
+ long length = stream.Length;
- if (bytesRead == 0)
- {
- break;
- }
+ if (length <= 0)
+ {
+ break;
+ }
- Array.Copy(buffer, 0, body, position, bytesRead);
+ if (length > buffer.Length)
+ {
+ length = buffer.Length;
+ }
- position += bytesRead;
- }
+ long remaining = bodySize - position;
+ if (length > remaining)
+ {
+ length = remaining;
+ }
- return body;
+ int bytesRead = stream.Read(buffer, 0, (int)length);
+
+ if (bytesRead == 0)
+ {
+ break;
+ }
+
+ Array.Copy(buffer, 0, body, position, bytesRead);
+
+ position += bytesRead;
+ }
+
+ return body;
+ }
+ catch
+ {
+ return null;
+ }
}
}
}
diff --git a/nanoFramework.WebServer/WebServer.cs b/nanoFramework.WebServer/WebServer.cs
index c25ef68..93756b1 100644
--- a/nanoFramework.WebServer/WebServer.cs
+++ b/nanoFramework.WebServer/WebServer.cs
@@ -10,6 +10,7 @@
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Security;
+using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
@@ -664,7 +665,20 @@ private void StartListener()
_listener.Start();
while (!_cancel)
{
- HttpListenerContext context = _listener.GetContext();
+ HttpListenerContext context;
+
+ try
+ {
+ context = _listener.GetContext();
+ }
+ catch (SocketException)
+ {
+ // possibly a transient connection error
+ // (e.g. OS captive-portal probe reset before request line is received)
+ // this allows continue and accepting the next connection
+ continue;
+ }
+
if (context == null)
{
return;
@@ -672,143 +686,159 @@ private void StartListener()
new Thread(() =>
{
- //This is for handling with transitory or bad requests
- if (context.Request.RawUrl == null)
+ try
{
- return;
- }
+ //This is for handling with transitory or bad requests
+ if (context.Request.RawUrl == null)
+ {
+ return;
+ }
- CallbackRoutes selectedRoute = null;
- bool selectedRouteHasAuth = false;
- string multipleCallback = null;
- bool hasAuthRoutes = false;
- string basicAuthNoCred = null;
- bool authFailed = false;
+ CallbackRoutes selectedRoute = null;
+ bool selectedRouteHasAuth = false;
+ string multipleCallback = null;
+ bool hasAuthRoutes = false;
+ string basicAuthNoCred = null;
+ bool authFailed = false;
- // Variables used only within the "for". They are here for performance reasons
- bool mustAuthenticate;
- bool isAuthOk;
+ // Variables used only within the "for". They are here for performance reasons
+ bool mustAuthenticate;
+ bool isAuthOk;
- foreach (CallbackRoutes route in _callbackRoutes)
- {
- if (!IsRouteMatch(route, context.Request.HttpMethod, context.Request.RawUrl))
+ foreach (CallbackRoutes route in _callbackRoutes)
{
- continue;
- }
+ if (!IsRouteMatch(route, context.Request.HttpMethod, context.Request.RawUrl))
+ {
+ continue;
+ }
- // Check auth first
- mustAuthenticate = route.Authentication != null && route.Authentication.AuthenticationType != AuthenticationType.None;
- if (mustAuthenticate)
- {
- hasAuthRoutes = true;
- if (route.Authentication.AuthenticationType == AuthenticationType.Basic)
+ // Check auth first
+ mustAuthenticate = route.Authentication != null && route.Authentication.AuthenticationType != AuthenticationType.None;
+ if (mustAuthenticate)
{
- var credReq = context.Request.Credentials;
- if (credReq is null)
+ hasAuthRoutes = true;
+ if (route.Authentication.AuthenticationType == AuthenticationType.Basic)
{
- if (basicAuthNoCred is null)
+ var credReq = context.Request.Credentials;
+ if (credReq is null)
{
- basicAuthNoCred = route.Route;
+ if (basicAuthNoCred is null)
+ {
+ basicAuthNoCred = route.Route;
+ }
+
+ continue;
}
- continue;
+ var credSite = route.Authentication.Credentials ?? Credential;
+
+ isAuthOk = credSite != null
+ && (credSite.UserName == credReq.UserName)
+ && (credSite.Password == credReq.Password);
}
+ else if (route.Authentication.AuthenticationType == AuthenticationType.ApiKey)
+ {
+ var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
+ if (apikeyReq is null)
+ {
+ continue;
+ }
- var credSite = route.Authentication.Credentials ?? Credential;
+ var apikeySite = route.Authentication.ApiKey ?? ApiKey;
- isAuthOk = credSite != null
- && (credSite.UserName == credReq.UserName)
- && (credSite.Password == credReq.Password);
- }
- else if (route.Authentication.AuthenticationType == AuthenticationType.ApiKey)
- {
- var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
- if (apikeyReq is null)
+ isAuthOk = apikeyReq == apikeySite;
+ }
+ else
{
- continue;
+ isAuthOk = false;
}
- var apikeySite = route.Authentication.ApiKey ?? ApiKey;
+ if (isAuthOk)
+ {
+ // This route can be used and has precedence over non-authenticated routes
+ if (!selectedRouteHasAuth)
+ {
+ selectedRoute = null;
+ multipleCallback = null;
+ }
+
+ selectedRouteHasAuth = true;
+ }
+ else
+ {
+ authFailed = true;
+ continue;
+ }
+ }
+ else if (selectedRouteHasAuth || authFailed)
+ {
+ // The selected route has authentication and/or a route exists with failed authentication.
+ // Those have precedence over non-authenticated routes
+ continue;
+ }
- isAuthOk = apikeyReq == apikeySite;
+ if (selectedRoute is null)
+ {
+ selectedRoute = route;
}
else
{
- isAuthOk = false;
+ multipleCallback ??= $"Multiple matching callbacks: {selectedRoute.Callback.DeclaringType.FullName}.{selectedRoute.Callback.Name}";
+ multipleCallback += $", {route.Callback.DeclaringType.FullName}.{route.Callback.Name}";
}
+ }
+
- if (isAuthOk)
+ if (selectedRoute is null)
+ {
+ if (hasAuthRoutes)
{
- // This route can be used and has precedence over non-authenticated routes
- if (!selectedRouteHasAuth)
+ if (!authFailed && basicAuthNoCred is not null)
{
- selectedRoute = null;
- multipleCallback = null;
+ context.Response.Headers.Add("WWW-Authenticate",
+ $"Basic realm=\"Access to {basicAuthNoCred}\"");
}
- selectedRouteHasAuth = true;
+ context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ context.Response.ContentLength64 = 0;
+ }
+ else if (CommandReceived != null)
+ {
+ // Starting a new thread to be able to handle a new request in parallel
+ CommandReceived.Invoke(this, new WebServerEventArgs(context));
}
else
{
- authFailed = true;
- continue;
+ context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+ context.Response.ContentLength64 = 0;
}
}
- else if (selectedRouteHasAuth || authFailed)
- {
- // The selected route has authentication and/or a route exists with failed authentication.
- // Those have precedence over non-authenticated routes
- continue;
- }
-
- if (selectedRoute is null)
+ else if (multipleCallback is not null)
{
- selectedRoute = route;
+ multipleCallback += ".";
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ OutputAsStream(context.Response, multipleCallback);
}
else
{
- multipleCallback ??= $"Multiple matching callbacks: {selectedRoute.Callback.DeclaringType.FullName}.{selectedRoute.Callback.Name}";
- multipleCallback += $", {route.Callback.DeclaringType.FullName}.{route.Callback.Name}";
+ InvokeRoute(selectedRoute, context);
}
- }
-
- if (selectedRoute is null)
+ HandleContextResponse(context);
+ }
+ catch (Exception ex)
{
- if (hasAuthRoutes)
- {
- if (!authFailed && basicAuthNoCred is not null)
- {
- context.Response.Headers.Add("WWW-Authenticate",
- $"Basic realm=\"Access to {basicAuthNoCred}\"");
- }
- context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
- context.Response.ContentLength64 = 0;
- }
- else if (CommandReceived != null)
+ try
{
- // Starting a new thread to be able to handle a new request in parallel
- CommandReceived.Invoke(this, new WebServerEventArgs(context));
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ context.Response.ContentLength64 = 0;
}
- else
+ catch
{
- context.Response.StatusCode = (int)HttpStatusCode.NotFound;
- context.Response.ContentLength64 = 0;
+ // Response may already be partially sent or the connection closed; nothing to do.
}
}
- else if (multipleCallback is not null)
- {
- multipleCallback += ".";
- context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
- OutputAsStream(context.Response, multipleCallback);
- }
- else
- {
- InvokeRoute(selectedRoute, context);
- }
-
- HandleContextResponse(context);
}).Start();
}