diff --git a/src/Entity/HttpHeaderCollection.cs b/src/Entity/HttpHeaderCollection.cs
index 75033e8..3b5d2a0 100644
--- a/src/Entity/HttpHeaderCollection.cs
+++ b/src/Entity/HttpHeaderCollection.cs
@@ -9,6 +9,7 @@
using Sisk.Core.Http;
using System.Collections.Specialized;
+using System.Text;
using Header = Sisk.Core.Internal.HttpKnownHeaderNames;
namespace Sisk.Core.Entity;
@@ -34,6 +35,17 @@ public HttpHeaderCollection(NameValueCollection headers) : base(headers)
{
}
+ ///
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (string key in this.Keys)
+ {
+ sb.AppendLine($"{key}: {this[key]}");
+ }
+ return sb.ToString();
+ }
+
///
/// Gets or sets the value of the HTTP Accept header.
///
diff --git a/src/Entity/MultipartFormCollection.cs b/src/Entity/MultipartFormCollection.cs
index 10f8e40..d5cba55 100644
--- a/src/Entity/MultipartFormCollection.cs
+++ b/src/Entity/MultipartFormCollection.cs
@@ -9,6 +9,7 @@
using System.Collections;
using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace Sisk.Core.Entity;
@@ -16,7 +17,7 @@ namespace Sisk.Core.Entity;
///
/// Represents an class which hosts an multipart form data contents.
///
-public sealed class MultipartFormCollection : IEnumerable, IReadOnlyList, IReadOnlyCollection
+public sealed class MultipartFormCollection : IReadOnlyList, IReadOnlyDictionary
{
private readonly IList _items;
@@ -61,23 +62,83 @@ public StringValue GetStringValue(string name)
public MultipartObject this[int index] => ((IReadOnlyList)_items)[index];
///
+ ///
+ public MultipartObject this[string name] => GetItem(name, false) ?? throw new KeyNotFoundException();
+
///
public int Count => ((IReadOnlyCollection)_items).Count;
- ///
+
+ ///
+ public IEnumerable Keys
+ {
+ get
+ {
+ for (int i = 0; i < _items.Count; i++)
+ {
+ MultipartObject? item = _items[i];
+ yield return item.Name;
+ }
+ }
+ }
+
+
+ ///
+ public IEnumerable Values
+ {
+ get
+ {
+ for (int i = 0; i < _items.Count; i++)
+ {
+ MultipartObject? item = _items[i];
+ yield return item;
+ }
+ }
+ }
+
///
public IEnumerator GetEnumerator()
{
return ((IEnumerable)_items).GetEnumerator();
}
- ///
///
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_items).GetEnumerator();
}
+ ///
+ public bool ContainsKey(string key)
+ {
+ return _items.Any(i => i.Name.CompareTo(key) == 0);
+ }
+
+ ///
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out MultipartObject value)
+ {
+ var i = _items.FirstOrDefault(item => item.Name.CompareTo(key) == 0);
+ if (i is null)
+ {
+ value = default;
+ return false;
+ }
+ else
+ {
+ value = i;
+ return true;
+ }
+ }
+
+ IEnumerator> IEnumerable>.GetEnumerator()
+ {
+ for (int i = 0; i < _items.Count; i++)
+ {
+ MultipartObject? item = _items[i];
+ yield return new KeyValuePair(item.Name, item);
+ }
+ }
+
///
///
public static implicit operator MultipartObject[](MultipartFormCollection t)
diff --git a/src/Entity/MultipartFormReader.cs b/src/Entity/MultipartFormReader.cs
new file mode 100644
index 0000000..840ae28
--- /dev/null
+++ b/src/Entity/MultipartFormReader.cs
@@ -0,0 +1,203 @@
+// The Sisk Framework source code
+// Copyright (c) 2023 PROJECT PRINCIPIUM
+//
+// The code below is licensed under the MIT license as
+// of the date of its publication, available at
+//
+// File name: MultipartFormReader.cs
+// Repository: https://github.com/sisk-http/core
+
+using Sisk.Core.Internal;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sisk.Core.Entity;
+
+internal sealed class MultipartFormReader
+{
+ byte[] boundaryBytes;
+ byte[] bytes;
+ byte[] nlbytes;
+ int position = 0;
+ Encoding encoder;
+ bool debugEnabled;
+
+ public MultipartFormReader(byte[] inputBytes, byte[] boundaryBytes, Encoding baseEncoding, bool debugEnabled)
+ {
+ this.boundaryBytes = boundaryBytes;
+ this.encoder = baseEncoding;
+ this.bytes = inputBytes;
+ this.position = 0;
+ this.nlbytes = baseEncoding.GetBytes("\r\n");
+ this.debugEnabled = debugEnabled;
+ }
+
+ void ThrowDataException(string message)
+ {
+ if (debugEnabled)
+ {
+ throw new InvalidDataException(SR.Format(SR.MultipartFormReader_InvalidData, position, message));
+ }
+ }
+
+ bool CanRead { get => position < bytes.Length; }
+
+ int ReadByte()
+ {
+ if (CanRead)
+ return bytes[position++];
+
+ return -1;
+ }
+
+ void ReadNewLine()
+ {
+ position += nlbytes.Length;
+ }
+
+ int Read(Span buffer)
+ {
+ int read = 0;
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ if (ReadByte() is > 0 and int b)
+ {
+ buffer[read++] = (byte)b;
+ }
+ else break;
+ }
+ return read;
+ }
+
+ public MultipartObject[] Read()
+ {
+ List objects = new List();
+ while (this.CanRead)
+ {
+ ReadNextBoundary();
+ NameValueCollection headers = ReadHeaders();
+
+ if (!CanRead)
+ break;
+
+ byte[] content = ReadContent().ToArray();
+
+ ReadNewLine();
+
+ string? contentDisposition = headers[HttpKnownHeaderNames.ContentDisposition];
+ if (contentDisposition is null)
+ {
+ ThrowDataException("The Content-Disposition header is empty or missing.");
+ continue;
+ }
+
+ NameValueCollection cdispositionValues = CookieParser.ParseCookieString(contentDisposition);
+ string? formItemName = CookieParser.RemoveValueQuotes(cdispositionValues["name"]);
+ string? formFilename = CookieParser.RemoveValueQuotes(cdispositionValues["filename"]);
+
+ if (string.IsNullOrEmpty(formItemName))
+ {
+ ThrowDataException("The Content-Disposition \"name\" parameter is empty or missing.");
+ continue;
+ }
+
+ MultipartObject resultObj = new MultipartObject(headers, formFilename, formItemName, content, encoder);
+
+ objects.Add(resultObj);
+ }
+
+ return objects.ToArray();
+ }
+
+ string ReadLine()
+ {
+ Span line = stackalloc byte[2048];
+ int read,
+ n = 0,
+ lnbytelen = nlbytes.Length;
+
+ while ((read = ReadByte()) > 0)
+ {
+ if (n == line.Length)
+ {
+ ThrowDataException($"Header line was too long (> {line.Length} bytes allocated).");
+ break;
+ }
+
+ line[n++] = (byte)read;
+
+ if (n >= lnbytelen)
+ {
+ if (line[(n - lnbytelen)..n].SequenceEqual(nlbytes))
+ {
+ break;
+ }
+ }
+ }
+
+ return encoder.GetString(line[0..n]);
+ }
+
+ Span ReadContent()
+ {
+ int boundaryLen = boundaryBytes.Length;
+ int istart = position;
+
+ while (CanRead)
+ {
+ position++;
+
+ if ((position - istart) > boundaryLen)
+ {
+ if (bytes[(position - boundaryLen)..position].SequenceEqual(boundaryBytes))
+ {
+ break;
+ }
+ }
+ }
+
+ position -= boundaryLen + nlbytes.Length + 2 /* the boundary "--" construct */;
+
+ return bytes[istart..position];
+ }
+
+ NameValueCollection ReadHeaders()
+ {
+ NameValueCollection headers = new NameValueCollection();
+ string? line;
+ while (!string.IsNullOrEmpty(line = ReadLine()))
+ {
+ int sepIndex = line.IndexOf(':');
+ if (sepIndex == -1)
+ break;
+
+ string hname = line.Substring(0, sepIndex);
+ string hvalue = line.Substring(sepIndex + 1).Trim();
+
+ headers.Add(hname, hvalue);
+ }
+
+ return headers;
+ }
+
+ unsafe void ReadNextBoundary()
+ {
+ Span boundaryBlock = stackalloc byte[boundaryBytes.Length + 2];
+ int nextLine = Read(boundaryBlock);
+
+ ReadNewLine();
+
+ if (nextLine != boundaryBlock.Length)
+ {
+ ThrowDataException($"Boundary expected at byte {position}.");
+ }
+ if (!boundaryBlock[2..].SequenceEqual(boundaryBytes))
+ {
+ ThrowDataException($"The provided boundary string does not match the request boundary string.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Entity/MultipartObject.cs b/src/Entity/MultipartObject.cs
index 28cb30c..947957a 100644
--- a/src/Entity/MultipartObject.cs
+++ b/src/Entity/MultipartObject.cs
@@ -22,50 +22,56 @@ public sealed class MultipartObject
private readonly Encoding _baseEncoding;
///
- /// The multipart form data object headers.
+ /// Gets this headers.
///
- public NameValueCollection Headers { get; private set; }
+ public HttpHeaderCollection Headers { get; private set; }
///
- /// The name of the file provided by Multipart form data. Null is returned if the object is not a file.
+ /// Gets this provided file name. If this object ins't disposing a file,
+ /// nothing is returned.
///
public string? Filename { get; private set; }
///
- /// The multipart form data object field name.
+ /// Gets this field name.
///
public string Name { get; private set; }
///
- /// The multipart form data content bytes.
+ /// Gets this form data content in bytes.
///
public byte[] ContentBytes { get; private set; }
///
- /// The multipart form data content length.
+ /// Gets this form data content length in byte count.
///
public int ContentLength { get; private set; }
+ ///
+ /// Gets an booolean indicating if this has contents or not.
+ ///
+ public bool HasContents { get => ContentLength > 0; }
+
///
/// Reads the content bytes with the given encoder.
///
- public string? ReadContentAsString(Encoding encoder)
+ public string ReadContentAsString(Encoding encoder)
{
if (ContentLength == 0)
- return null;
+ return string.Empty;
return encoder.GetString(ContentBytes);
}
///
/// Reads the content bytes using the HTTP request content-encoding.
///
- public string? ReadContentAsString()
+ public string ReadContentAsString()
{
return ReadContentAsString(_baseEncoding);
}
///
- /// Determine the image format based in the file header for each image content type.
+ /// Determines the image format based in the file header for each image content type.
///
public MultipartObjectCommonFormat GetCommonFileFormat()
{
@@ -79,7 +85,7 @@ public MultipartObjectCommonFormat GetCommonFileFormat()
{
return MultipartObjectCommonFormat.PNG;
}
- }
+ }
if (byteLen >= 4)
{
Span len4 = ContentBytes.AsSpan(0, 4);
@@ -124,7 +130,7 @@ public MultipartObjectCommonFormat GetCommonFileFormat()
internal MultipartObject(NameValueCollection headers, string? filename, string name, byte[]? body, Encoding encoding)
{
- Headers = headers;
+ Headers = new HttpHeaderCollection(headers);
Filename = filename;
Name = name;
ContentBytes = body ?? Array.Empty();
@@ -145,8 +151,9 @@ internal static MultipartFormCollection ParseMultipartObjects(HttpRequest req)
string[] contentTypePieces = contentType.Split(';');
string? boundary = null;
- foreach (string obj in contentTypePieces)
+ for (int i = 0; i < contentTypePieces.Length; i++)
{
+ string obj = contentTypePieces[i];
string[] kv = obj.Split("=");
if (kv.Length != 2)
{ continue; }
@@ -163,6 +170,14 @@ internal static MultipartFormCollection ParseMultipartObjects(HttpRequest req)
byte[] boundaryBytes = Encoding.UTF8.GetBytes(boundary);
+ if (req.baseServer.ServerConfiguration.Flags.EnableNewMultipartFormReader == true)
+ {
+ MultipartFormReader reader = new MultipartFormReader(req.RawBody, boundaryBytes, req.RequestEncoding, req.baseServer.ServerConfiguration.ThrowExceptions);
+ var objects = reader.Read();
+
+ return new MultipartFormCollection(objects);
+ }
+
/////////
// https://stackoverflow.com/questions/9755090/split-a-byte-array-at-a-delimiter
byte[][] Separate(byte[] source, byte[] separator)
@@ -271,8 +286,9 @@ bool Equals(byte[] source, byte[] separator, int index)
string? fieldName = null;
string? fieldFilename = null;
- foreach (string valueAttribute in val)
+ for (int k = 0; k < val.Length; k++)
{
+ string valueAttribute = val[k];
string[] valAttributeParts = valueAttribute.Trim().Split("=");
if (valAttributeParts.Length != 2)
continue;
diff --git a/src/Entity/StringValue.cs b/src/Entity/StringValue.cs
index fff4bc1..0d2eed4 100644
--- a/src/Entity/StringValue.cs
+++ b/src/Entity/StringValue.cs
@@ -71,6 +71,18 @@ internal StringValue(string name, string type, string? data)
return this;
}
+ ///
+ /// Gets an object representation from this , parsing the current string expression into an value of
+ /// . This method will throw an if
+ /// the value stored in this instance is null.
+ ///
+ /// The type.
+ public TEnum GetEnum() where TEnum : struct, Enum
+ {
+ ThrowIfNull();
+ return Enum.Parse(_ref!, true);
+ }
+
///
/// Gets a non-null string from this . This method will throw an if
/// the value stored in this instance is null.
diff --git a/src/Http/HttpRequest.cs b/src/Http/HttpRequest.cs
index 02aec7b..1982129 100644
--- a/src/Http/HttpRequest.cs
+++ b/src/Http/HttpRequest.cs
@@ -25,6 +25,7 @@ namespace Sisk.Core.Http
public class HttpRequestException : Exception
{
internal HttpRequestException(string message) : base(message) { }
+ internal HttpRequestException(string message, Exception? innerException) : base(message, innerException) { }
}
///
@@ -167,41 +168,8 @@ public NameValueCollection Cookies
{
if (cookies is null)
{
- cookies = new NameValueCollection();
string? cookieHeader = listenerRequest.Headers[HttpKnownHeaderNames.Cookie];
-
- if (!string.IsNullOrWhiteSpace(cookieHeader))
- {
- string[] cookiePairs = cookieHeader.Split(';');
-
- for (int i = 0; i < cookiePairs.Length; i++)
- {
- string cookieExpression = cookiePairs[i];
-
- if (string.IsNullOrWhiteSpace(cookieExpression))
- continue;
-
- int eqPos = cookieExpression.IndexOf('=');
- if (eqPos < 0)
- {
- cookies[cookieExpression] = "";
- continue;
- }
- else
- {
- string cookieName = cookieExpression.Substring(0, eqPos).Trim();
- string cookieValue = cookieExpression.Substring(eqPos + 1).Trim();
-
- if (string.IsNullOrWhiteSpace(cookieName))
- {
- // provided an name/value pair, but no name
- continue;
- }
-
- cookies[cookieName] = WebUtility.UrlDecode(cookieValue);
- }
- }
- }
+ cookies = CookieParser.ParseCookieString(cookieHeader);
}
return cookies;
@@ -355,7 +323,14 @@ public IPAddress RemoteAddress
///
public MultipartFormCollection GetMultipartFormContent()
{
- return MultipartObject.ParseMultipartObjects(this);
+ try
+ {
+ return MultipartObject.ParseMultipartObjects(this);
+ }
+ catch (Exception ex)
+ {
+ throw new HttpRequestException(SR.Format(SR.MultipartFormReader_Exception, ex.Message), ex);
+ }
}
///
@@ -402,7 +377,14 @@ public string GetRawHttpRequest(bool includeBody = true, bool appendExtraInfo =
// Content
if (includeBody)
{
- sb.Append(Body);
+ if (Body.Length < 8 * HttpServer.UnitKb)
+ {
+ sb.Append(Body);
+ }
+ else
+ {
+ sb.Append($"| ({HttpServer.HumanReadableSize(Body.Length)} bytes)");
+ }
}
return sb.ToString();
diff --git a/src/Http/HttpResponse.cs b/src/Http/HttpResponse.cs
index ddda57f..41928c9 100644
--- a/src/Http/HttpResponse.cs
+++ b/src/Http/HttpResponse.cs
@@ -119,7 +119,19 @@ public string GetRawHttpResponse(bool includeBody = true)
if (includeBody && Content is not StreamContent)
{
- sb.Append(Content?.ReadAsStringAsync().Result);
+ string? s = Content?.ReadAsStringAsync().Result;
+
+ if (s is not null)
+ {
+ if (s.Length < 8 * HttpServer.UnitKb)
+ {
+ sb.Append(s);
+ }
+ else
+ {
+ sb.Append($"| ({HttpServer.HumanReadableSize(s.Length)} bytes)");
+ }
+ }
}
return sb.ToString();
diff --git a/src/Http/HttpServerFlags.cs b/src/Http/HttpServerFlags.cs
index 385a6d9..6f59afd 100644
--- a/src/Http/HttpServerFlags.cs
+++ b/src/Http/HttpServerFlags.cs
@@ -126,6 +126,15 @@ public sealed class HttpServerFlags
///
public TimeSpan IdleConnectionTimeout = TimeSpan.FromSeconds(120);
+ ///
+ /// Determines if the new span-based multipart form reader should be used. This is an experimental
+ /// feature and may not be stable for production usage.
+ ///
+ /// Default value: false
+ ///
+ ///
+ public bool EnableNewMultipartFormReader = false;
+
///
/// Creates an new instance with default flags values.
///
diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs
index e19c211..19298d5 100644
--- a/src/Http/HttpServer__Core.cs
+++ b/src/Http/HttpServer__Core.cs
@@ -19,13 +19,13 @@ namespace Sisk.Core.Http;
public partial class HttpServer
{
- const long UnitKb = 1024;
- const long UnitMb = UnitKb * 1024;
- const long UnitGb = UnitMb * 1024;
- const long UnitTb = UnitGb * 1024;
+ internal const long UnitKb = 1024;
+ internal const long UnitMb = UnitKb * 1024;
+ internal const long UnitGb = UnitMb * 1024;
+ internal const long UnitTb = UnitGb * 1024;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static string HumanReadableSize(float? size)
+ internal static string HumanReadableSize(in float size)
{
if (size < UnitKb)
{
@@ -202,7 +202,7 @@ private void ProcessRequest(HttpListenerContext context)
executionResult.Request = request;
otherParty = request.RemoteAddress;
- if (!matchedListeningHost.CanListen)
+ if (matchedListeningHost.Router is null)
{
baseResponse.StatusCode = 503; // Service Unavailable
executionResult.Status = HttpServerExecutionStatus.ListeningHostNotReady;
@@ -210,7 +210,7 @@ private void ProcessRequest(HttpListenerContext context)
}
else
{
- matchedListeningHost.Router!.BindServer(this);
+ matchedListeningHost.Router.BindServer(this);
}
#endregion // 27668
@@ -219,6 +219,7 @@ private void ProcessRequest(HttpListenerContext context)
if (ServerConfiguration.IncludeRequestIdHeader)
baseResponse.Headers.Set(flag.HeaderNameRequestId, request.RequestId.ToString());
+
if (flag.SendSiskHeader)
baseResponse.Headers.Set(HttpKnownHeaderNames.XPoweredBy, PoweredBy);
@@ -492,6 +493,7 @@ response.Content is StreamContent ||
{
ServerConfiguration.ErrorsLogsStream?.WriteException(executionResult.ServerException);
}
+
if (canAccessLog)
{
var formatter = new LoggingFormatter(
diff --git a/src/Http/ListeningHostRepository.cs b/src/Http/ListeningHostRepository.cs
index 9dd89c7..3174400 100644
--- a/src/Http/ListeningHostRepository.cs
+++ b/src/Http/ListeningHostRepository.cs
@@ -115,7 +115,7 @@ IEnumerator IEnumerable.GetEnumerator()
/// The Listening Host index
public ListeningHost this[int index] { get => _hosts[index]; set => _hosts[index] = value; }
- internal ListeningHost? GetRequestMatchingListeningHost(string incomingHost, int incomingPort)
+ internal ListeningHost? GetRequestMatchingListeningHost(string incomingHost, in int incomingPort)
{
lock (_hosts)
{
diff --git a/src/Http/Streams/HttpStreamPingPolicy.cs b/src/Http/Streams/HttpStreamPingPolicy.cs
index 9c0a250..d6f5fdc1 100644
--- a/src/Http/Streams/HttpStreamPingPolicy.cs
+++ b/src/Http/Streams/HttpStreamPingPolicy.cs
@@ -26,7 +26,7 @@ public sealed class HttpStreamPingPolicy
///
/// Gets or sets the sending interval for each ping message.
///
- public TimeSpan Interval { get; set; } = TimeSpan.FromSeconds(1);
+ public TimeSpan Interval { get; set; } = TimeSpan.FromSeconds(5);
internal HttpStreamPingPolicy(HttpRequestEventSource parent)
{
diff --git a/src/Internal/CookieParser.cs b/src/Internal/CookieParser.cs
new file mode 100644
index 0000000..6ed108a
--- /dev/null
+++ b/src/Internal/CookieParser.cs
@@ -0,0 +1,78 @@
+// The Sisk Framework source code
+// Copyright (c) 2023 PROJECT PRINCIPIUM
+//
+// The code below is licensed under the MIT license as
+// of the date of its publication, available at
+//
+// File name: CookieParser.cs
+// Repository: https://github.com/sisk-http/core
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sisk.Core.Internal;
+
+internal static class CookieParser
+{
+ public static string? RemoveValueQuotes(string? value)
+ {
+ if (value is null)
+ return null;
+
+ const char QUOTE = '"';
+
+ if (value.StartsWith(QUOTE))
+ {
+ value = value[1..];
+ }
+ if (value.EndsWith(QUOTE))
+ {
+ value = value[..^1];
+ }
+ return value;
+ }
+
+ public static NameValueCollection ParseCookieString(string? cookieHeader)
+ {
+ NameValueCollection cookies = new NameValueCollection();
+ if (!string.IsNullOrWhiteSpace(cookieHeader))
+ {
+ string[] cookiePairs = cookieHeader.Split(';');
+
+ for (int i = 0; i < cookiePairs.Length; i++)
+ {
+ string cookieExpression = cookiePairs[i];
+
+ if (string.IsNullOrWhiteSpace(cookieExpression))
+ continue;
+
+ int eqPos = cookieExpression.IndexOf('=');
+ if (eqPos < 0)
+ {
+ cookies[cookieExpression] = "";
+ continue;
+ }
+ else
+ {
+ string cookieName = cookieExpression.Substring(0, eqPos).Trim();
+ string cookieValue = cookieExpression.Substring(eqPos + 1).Trim();
+
+ if (string.IsNullOrWhiteSpace(cookieName))
+ {
+ // provided an name/value pair, but no name
+ continue;
+ }
+
+ cookies[cookieName] = WebUtility.UrlDecode(cookieValue);
+ }
+ }
+ }
+
+ return cookies;
+ }
+}
diff --git a/src/Internal/HttpStringInternals.cs b/src/Internal/HttpStringInternals.cs
index bc14e15..3e373cb 100644
--- a/src/Internal/HttpStringInternals.cs
+++ b/src/Internal/HttpStringInternals.cs
@@ -122,7 +122,7 @@ public static PathMatchResult IsPathMatch(ReadOnlySpan pathPattern, ReadOn
return new PathMatchResult(true, query);
}
#else
- public static bool PathRouteMatch(string routeA, string routeB, bool ignoreCase)
+ public static bool PathRouteMatch(in string routeA, in string routeB, bool ignoreCase)
{
if (routeA == Route.AnyPath || routeB == Route.AnyPath)
{
@@ -160,7 +160,7 @@ public static bool PathRouteMatch(string routeA, string routeB, bool ignoreCase)
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public static PathMatchResult IsPathMatch(string pathPattern, string requestPath, bool ignoreCase)
+ public static PathMatchResult IsPathMatch(in string pathPattern, in string requestPath, bool ignoreCase)
{
if (pathPattern == Route.AnyPath)
{
@@ -201,7 +201,7 @@ public static PathMatchResult IsPathMatch(string pathPattern, string requestPath
}
#endif
- public static bool IsDnsMatch(string wildcardPattern, string subject)
+ public static bool IsDnsMatch(in string wildcardPattern, string subject)
{
StringComparison comparer = StringComparison.OrdinalIgnoreCase;
@@ -245,7 +245,7 @@ public static bool IsDnsMatch(string wildcardPattern, string subject)
}
}
- private static bool isWildcardMatchRgx(string pattern, string subject, StringComparison comparer)
+ private static bool isWildcardMatchRgx(in string pattern, in string subject, StringComparison comparer)
{
string[] parts = pattern.Split('*');
if (parts.Length <= 1)
diff --git a/src/Internal/LoggingConstants.cs b/src/Internal/LoggingConstants.cs
index b9d14e6..566b191 100644
--- a/src/Internal/LoggingConstants.cs
+++ b/src/Internal/LoggingConstants.cs
@@ -26,8 +26,8 @@ internal class LoggingFormatter
readonly int bResStatusCode;
readonly string? bResStatusDescr;
readonly string bReqMethod;
- readonly float? incomingSize;
- readonly float? outcomingSize;
+ readonly long incomingSize;
+ readonly long outcomingSize;
readonly long execTime;
public LoggingFormatter(
@@ -47,8 +47,8 @@ public LoggingFormatter(
this.reqHeaders = reqHeaders;
this.bResStatusCode = bResStatusCode;
this.bResStatusDescr = bResStatusDescr;
- incomingSize = res.RequestSize;
- outcomingSize = res.ResponseSize;
+ this.incomingSize = res.RequestSize;
+ this.outcomingSize = res.ResponseSize;
this.execTime = execTime;
this.bReqMethod = bReqMethod;
}
diff --git a/src/Internal/SR.cs b/src/Internal/SR.cs
index 0a9542c..93bda01 100644
--- a/src/Internal/SR.cs
+++ b/src/Internal/SR.cs
@@ -12,6 +12,8 @@ static partial class SR
public const string MultipartObject_ContentTypeMissing = "Content-Type header cannot be null when retriving a multipart form content";
public const string MultipartObject_BoundaryMissing = "No boundary was specified for this multipart form content.";
public const string MultipartObject_EmptyFieldName = "Content-part object position {0} cannot have an empty field name.";
+ public const string MultipartFormReader_InvalidData = "Cannot read the specified multipart-form data request. At byte position {0}: {1}";
+ public const string MultipartFormReader_Exception = "Caught an exception while trying to read the multipart form request: {0}";
public const string HttpContextBagRepository_UndefinedDynamicProperty = "The specified type {0} was not defined in this context bag repository.";
diff --git a/src/Routing/Route.cs b/src/Routing/Route.cs
index 2b28bd3..6ddbfda 100644
--- a/src/Routing/Route.cs
+++ b/src/Routing/Route.cs
@@ -15,7 +15,7 @@ namespace Sisk.Core.Routing
///
/// Represents an HTTP route to be matched by an object.
///
- public sealed class Route
+ public class Route
{
internal RouteAction? _callback { get; set; }
internal bool isReturnTypeTask;
diff --git a/src/Routing/Router__CoreInvoker.cs b/src/Routing/Router__CoreInvoker.cs
index 997b757..98bf301 100644
--- a/src/Routing/Router__CoreInvoker.cs
+++ b/src/Routing/Router__CoreInvoker.cs
@@ -56,7 +56,7 @@ private Internal.HttpStringInternals.PathMatchResult TestRouteMatchUsingRegex(Ro
}
}
- internal bool InvokeRequestHandlerGroup(RequestHandlerExecutionMode mode, IRequestHandler[] baseLists, IRequestHandler[]? bypassList, HttpRequest request, HttpContext context, out HttpResponse? result, out Exception? exception)
+ internal bool InvokeRequestHandlerGroup(in RequestHandlerExecutionMode mode, IRequestHandler[] baseLists, IRequestHandler[]? bypassList, HttpRequest request, HttpContext context, out HttpResponse? result, out Exception? exception)
{
ref IRequestHandler pointer = ref MemoryMarshal.GetArrayDataReference(baseLists);
for (int i = 0; i < baseLists.Length; i++)
@@ -124,7 +124,7 @@ internal RouterExecutionResult Execute(HttpContext context)
ref Route rPointer = ref MemoryMarshal.GetReference(rspan);
for (int i = 0; i < rspan.Length; i++)
{
- Route route = Unsafe.Add(ref rPointer, i);
+ ref Route route = ref Unsafe.Add(ref rPointer, i);
// test path
HttpStringInternals.PathMatchResult pathTest;
diff --git a/src/Sisk.Core.csproj b/src/Sisk.Core.csproj
index 4c10697..571d861 100644
--- a/src/Sisk.Core.csproj
+++ b/src/Sisk.Core.csproj
@@ -31,7 +31,7 @@
1.0.0.0
1.0.0.0
- 1.0.0.0-rc3
+ 1.0.0.0-rc4
LICENSE.txt
False