diff --git a/src/Entity/CircularBuffer.cs b/src/Entity/CircularBuffer.cs index 66e398d..0ffdbb2 100644 --- a/src/Entity/CircularBuffer.cs +++ b/src/Entity/CircularBuffer.cs @@ -16,7 +16,7 @@ namespace Sisk.Core.Entity; /// /// The type of elements stored in the buffer. /// -public class CircularBuffer : IEnumerable, IReadOnlyList +public sealed class CircularBuffer : IEnumerable, IReadOnlyList { private T[] items; diff --git a/src/Entity/HttpHeaderCollection.cs b/src/Entity/HttpHeaderCollection.cs index 3b5d2a0..8d56eb1 100644 --- a/src/Entity/HttpHeaderCollection.cs +++ b/src/Entity/HttpHeaderCollection.cs @@ -8,7 +8,10 @@ // Repository: https://github.com/sisk-http/core using Sisk.Core.Http; +using System.Collections; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Reflection.PortableExecutable; using System.Text; using Header = Sisk.Core.Internal.HttpKnownHeaderNames; @@ -17,8 +20,11 @@ namespace Sisk.Core.Entity; /// /// Represents an collection of HTTP headers with their name and values. /// -public sealed class HttpHeaderCollection : NameValueCollection +public sealed class HttpHeaderCollection : IDictionary { + private List<(string headerName, string headerValue)> _headers = new(); + internal bool isReadOnly = false; + /// /// Create an new instance of the class. /// @@ -31,50 +37,264 @@ public HttpHeaderCollection() /// headers. /// /// The header collection. - public HttpHeaderCollection(NameValueCollection headers) : base(headers) + public HttpHeaderCollection(NameValueCollection headers) { + foreach (string headerName in headers) + { + string[]? values = headers.GetValues(headerName); + if (values is not null) + { + for (int i = 0; i < values.Length; i++) + { + string value = values[i]; + Add(headerName, value); + } + } + } } + /// public override string ToString() { StringBuilder sb = new StringBuilder(); - foreach (string key in this.Keys) + foreach (var item in _headers) { - sb.AppendLine($"{key}: {this[key]}"); + sb.AppendLine($"{item.headerName}: {item.headerValue}"); } return sb.ToString(); } + /// + public (string, string) this[int index] + { + get + { + var h = _headers[index]; + return (h.headerName, h.headerValue); + } + } + + /// + public string? this[string headerName] + { + get + { + return string.Join(", ", GetValues(headerName)); + } + set + { + Set(headerName, value); + } + } + + #region Setters + /// + public void Add(string name, string? value) + { + ThrowIfReadOnly(); + if (value is null) + return; + + _headers.Add((name, value)); + } + + /// + /// Sets the specified header to the specified value. + /// + /// + /// If the header specified in the header does not exist, the Set method inserts a new + /// header into the list of header name/value pairs. If the header specified in header is + /// already present, value replaces the existing value. + /// + /// The header name. + /// The header value. + public void Set(string headerName, string? headerValue) + { + Remove(headerName); + Add(headerName, headerValue); + } + #endregion + + #region Getters + /// + /// Gets the first headers value associated with the specified header name, or nothing it if no value + /// is associated with the specified header. + /// + /// The header name. + public string? GetValue(string name) + { + for (int i = 0; i < _headers.Count; i++) + { + var item = _headers[i]; + if (string.Compare(item.headerName, name, true) == 0) + { + return item.headerValue; + } + } + return null; + } + + /// + /// Gets all headers values associated with the specified header name. + /// + /// The header name. + public IEnumerable GetValues(string name) + { + for (int i = 0; i < _headers.Count; i++) + { + var item = _headers[i]; + if (string.Compare(item.headerName, name, true) == 0) + { + yield return item.headerValue; + } + } + } + #endregion + + #region Interface methods + + /// + public bool ContainsKey(string key) + { + for (int i = 0; i < _headers.Count; i++) + { + if (string.Compare(_headers[i].headerName, key, true) == 0) + { + return true; + } + } + return false; + } + + /// + public bool Remove(string key) + { + ThrowIfReadOnly(); + List toRemove = new List(); + for (int i = 0; i < _headers.Count; i++) + { + if (string.Compare(_headers[i].headerName, key, true) == 0) + { + toRemove.Add(i); + } + } + if (toRemove.Count > 0) + { + toRemove.Reverse(); + for (int i = 0; i < toRemove.Count; i++) + { + _headers.RemoveAt(i); + } + return true; + } + return false; + } + + /// + public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value) + { + for (int i = 0; i < _headers.Count; i++) + { + var item = _headers[i]; + if (string.Compare(item.headerName, key, true) == 0) + { + value = item.headerValue; + return true; + } + } + value = default; + return false; + } + + /// + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + /// + public void Clear() + { + ThrowIfReadOnly(); + _headers.Clear(); + } + + /// + public bool Contains(KeyValuePair item) + { + for (int i = 0; i < _headers.Count; i++) + { + var hitem = _headers[i]; + if (string.Compare(hitem.headerName, item.Key, true) == 0 && hitem.headerValue.Equals(item.Value)) + { + return true; + } + } + return false; + } + + /// + /// This method is not implemented and should not be used. + /// + [Obsolete("This method is not implemented and should not be used.")] + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + /// + public bool Remove(KeyValuePair item) + { + return Remove(item.Key); + } + + /// + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < _headers.Count; i++) + { + var item = _headers[i]; + yield return new KeyValuePair(item.headerName, item.headerValue); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region Helper properties /// /// Gets or sets the value of the HTTP Accept header. /// - public string? Accept { get => base[Header.Accept]; set => base[Header.Accept] = value; } + public string? Accept { get => this[Header.Accept]; set => this[Header.Accept] = value; } /// /// Gets or sets the value of the HTTP Accept-Charset header. /// - public string? AcceptCharset { get => base[Header.AcceptCharset]; set => base[Header.AcceptCharset] = value; } + public string? AcceptCharset { get => this[Header.AcceptCharset]; set => this[Header.AcceptCharset] = value; } /// /// Gets or sets the value of the HTTP Accept-Encoding header. /// - public string? AcceptEncoding { get => base[Header.AcceptEncoding]; set => base[Header.AcceptEncoding] = value; } + public string? AcceptEncoding { get => this[Header.AcceptEncoding]; set => this[Header.AcceptEncoding] = value; } /// /// Gets or sets the value of the HTTP Accept-Language header. /// - public string? AcceptLanguage { get => base[Header.AcceptLanguage]; set => base[Header.AcceptLanguage] = value; } + public string? AcceptLanguage { get => this[Header.AcceptLanguage]; set => this[Header.AcceptLanguage] = value; } /// /// Gets or sets the value of the HTTP Accept-Patch header. /// - public string? AcceptPatch { get => base[Header.AcceptPatch]; set => base[Header.AcceptPatch] = value; } + public string? AcceptPatch { get => this[Header.AcceptPatch]; set => this[Header.AcceptPatch] = value; } /// /// Gets or sets the value of the HTTP Accept-Ranges header. /// - public string? AcceptRanges { get => base[Header.AcceptRanges]; set => base[Header.AcceptRanges] = value; } + public string? AcceptRanges { get => this[Header.AcceptRanges]; set => this[Header.AcceptRanges] = value; } /// /// Gets or sets the value of the HTTP Access-Control-Allow-Credentials header. @@ -82,7 +302,7 @@ public override string ToString() /// /// Note: this header can be overwritten by the current configuration. /// - public string? AccessControlAllowCredentials { get => base[Header.AccessControlAllowCredentials]; set => base[Header.AccessControlAllowCredentials] = value; } + public string? AccessControlAllowCredentials { get => this[Header.AccessControlAllowCredentials]; set => this[Header.AccessControlAllowCredentials] = value; } /// /// Gets or sets the value of the HTTP Access-Control-Allow-Headers header. @@ -90,12 +310,12 @@ public override string ToString() /// /// Note: this header can be overwritten by the current configuration. /// - public string? AccessControlAllowHeaders { get => base[Header.AccessControlAllowHeaders]; set => base[Header.AccessControlAllowHeaders] = value; } + public string? AccessControlAllowHeaders { get => this[Header.AccessControlAllowHeaders]; set => this[Header.AccessControlAllowHeaders] = value; } /// /// Gets or sets the value of the HTTP Access-Control-Allow-Methods header. /// - public string? AccessControlAllowMethods { get => base[Header.AccessControlAllowMethods]; set => base[Header.AccessControlAllowMethods] = value; } + public string? AccessControlAllowMethods { get => this[Header.AccessControlAllowMethods]; set => this[Header.AccessControlAllowMethods] = value; } /// /// Gets or sets the value of the HTTP Access-Control-Allow-Origin header. @@ -103,7 +323,7 @@ public override string ToString() /// /// Note: this header can be overwritten by the current configuration. /// - public string? AccessControlAllowOrigin { get => base[Header.AccessControlAllowOrigin]; set => base[Header.AccessControlAllowOrigin] = value; } + public string? AccessControlAllowOrigin { get => this[Header.AccessControlAllowOrigin]; set => this[Header.AccessControlAllowOrigin] = value; } /// /// Gets or sets the value of the HTTP Access-Control-Expose-Headers header. @@ -111,7 +331,7 @@ public override string ToString() /// /// Note: this header can be overwritten by the current configuration. /// - public string? AccessControlExposeHeaders { get => base[Header.AccessControlExposeHeaders]; set => base[Header.AccessControlExposeHeaders] = value; } + public string? AccessControlExposeHeaders { get => this[Header.AccessControlExposeHeaders]; set => this[Header.AccessControlExposeHeaders] = value; } /// /// Gets or sets the value of the HTTP Access-Control-Max-Age header. @@ -119,52 +339,52 @@ public override string ToString() /// /// Note: this header can be overwritten by the current configuration. /// - public string? AccessControlMaxAge { get => base[Header.AccessControlMaxAge]; set => base[Header.AccessControlMaxAge] = value; } + public string? AccessControlMaxAge { get => this[Header.AccessControlMaxAge]; set => this[Header.AccessControlMaxAge] = value; } /// /// Gets or sets the value of the HTTP Age header. /// - public string? Age { get => base[Header.Age]; set => base[Header.Age] = value; } + public string? Age { get => this[Header.Age]; set => this[Header.Age] = value; } /// /// Gets or sets the value of the HTTP Allow header. /// - public string? Allow { get => base[Header.Allow]; set => base[Header.Allow] = value; } + public string? Allow { get => this[Header.Allow]; set => this[Header.Allow] = value; } /// /// Gets or sets the value of the HTTP Alt-Svc header. /// - public string? AltSvc { get => base[Header.AltSvc]; set => base[Header.AltSvc] = value; } + public string? AltSvc { get => this[Header.AltSvc]; set => this[Header.AltSvc] = value; } /// /// Gets or sets the value of the HTTP Authorization header. /// - public string? Authorization { get => base[Header.Authorization]; set => base[Header.Authorization] = value; } + public string? Authorization { get => this[Header.Authorization]; set => this[Header.Authorization] = value; } /// /// Gets or sets the value of the HTTP Cache-Control header. /// - public string? CacheControl { get => base[Header.CacheControl]; set => base[Header.CacheControl] = value; } + public string? CacheControl { get => this[Header.CacheControl]; set => this[Header.CacheControl] = value; } /// /// Gets or sets the value of the HTTP Content-Disposition header. /// - public string? ContentDisposition { get => base[Header.ContentDisposition]; set => base[Header.ContentDisposition] = value; } + public string? ContentDisposition { get => this[Header.ContentDisposition]; set => this[Header.ContentDisposition] = value; } /// /// Gets or sets the value of the HTTP Content-Encoding header. /// - public string? ContentEncoding { get => base[Header.ContentEncoding]; set => base[Header.ContentEncoding] = value; } + public string? ContentEncoding { get => this[Header.ContentEncoding]; set => this[Header.ContentEncoding] = value; } /// /// Gets or sets the value of the HTTP Content-Language header. /// - public string? ContentLanguage { get => base[Header.ContentLanguage]; set => base[Header.ContentLanguage] = value; } + public string? ContentLanguage { get => this[Header.ContentLanguage]; set => this[Header.ContentLanguage] = value; } /// /// Gets or sets the value of the HTTP Content-Range header. /// - public string? ContentRange { get => base[Header.ContentRange]; set => base[Header.ContentRange] = value; } + public string? ContentRange { get => this[Header.ContentRange]; set => this[Header.ContentRange] = value; } /// /// Gets or sets the value of the HTTP Content-Type header. @@ -172,7 +392,7 @@ public override string ToString() /// /// Note: setting the value of this header, the value present in the response's will be overwritten. /// - public string? ContentType { get => base[Header.ContentType]; set => base[Header.ContentType] = value; } + public string? ContentType { get => this[Header.ContentType]; set => this[Header.ContentType] = value; } /// /// Gets or sets the value of the HTTP Cookie header. @@ -181,121 +401,117 @@ public override string ToString() /// Tip: use property to getting cookies values from requests and /// on to set cookies. /// - public string? Cookie { get => base[Header.Cookie]; set => base[Header.Cookie] = value; } + public string? Cookie { get => this[Header.Cookie]; set => this[Header.Cookie] = value; } /// /// Gets or sets the value of the HTTP Expect header. /// - public string? Expect { get => base[Header.Expect]; set => base[Header.Expect] = value; } + public string? Expect { get => this[Header.Expect]; set => this[Header.Expect] = value; } /// /// Gets or sets the value of the HTTP Expires header. /// - public string? Expires { get => base[Header.Expires]; set => base[Header.Expires] = value; } + public string? Expires { get => this[Header.Expires]; set => this[Header.Expires] = value; } /// /// Gets or sets the value of the HTTP Host header. /// - public string? Host { get => base[Header.Host]; set => base[Header.Host] = value; } + public string? Host { get => this[Header.Host]; set => this[Header.Host] = value; } /// /// Gets or sets the value of the HTTP Origin header. /// - public string? Origin { get => base[Header.Origin]; set => base[Header.Origin] = value; } + public string? Origin { get => this[Header.Origin]; set => this[Header.Origin] = value; } /// /// Gets or sets the value of the HTTP Range header. /// - public string? Range { get => base[Header.Range]; set => base[Header.Range] = value; } + public string? Range { get => this[Header.Range]; set => this[Header.Range] = value; } /// /// Gets or sets the value of the HTTP Referer header. /// - public string? Referer { get => base[Header.Referer]; set => base[Header.Referer] = value; } + public string? Referer { get => this[Header.Referer]; set => this[Header.Referer] = value; } /// /// Gets or sets the value of the HTTP Retry-After header. /// - public string? RetryAfter { get => base[Header.RetryAfter]; set => base[Header.RetryAfter] = value; } + public string? RetryAfter { get => this[Header.RetryAfter]; set => this[Header.RetryAfter] = value; } /// /// Gets or sets the value of the HTTP If-Match header. /// - public string? IfMatch { get => base[Header.IfMatch]; set => base[Header.IfMatch] = value; } + public string? IfMatch { get => this[Header.IfMatch]; set => this[Header.IfMatch] = value; } /// /// Gets or sets the value of the HTTP If-None-Match header. /// - public string? IfNoneMatch { get => base[Header.IfNoneMatch]; set => base[Header.IfNoneMatch] = value; } + public string? IfNoneMatch { get => this[Header.IfNoneMatch]; set => this[Header.IfNoneMatch] = value; } /// /// Gets or sets the value of the HTTP If-Range header. /// - public string? IfRange { get => base[Header.IfRange]; set => base[Header.IfRange] = value; } + public string? IfRange { get => this[Header.IfRange]; set => this[Header.IfRange] = value; } /// /// Gets or sets the value of the HTTP If-Modified-Since header. /// - public string? IfModifiedSince { get => base[Header.IfModifiedSince]; set => base[Header.IfModifiedSince] = value; } + public string? IfModifiedSince { get => this[Header.IfModifiedSince]; set => this[Header.IfModifiedSince] = value; } /// /// Gets or sets the value of the HTTP If-Unmodified-Since header. /// - public string? IfUnmodifiedSince { get => base[Header.IfUnmodifiedSince]; set => base[Header.IfUnmodifiedSince] = value; } + public string? IfUnmodifiedSince { get => this[Header.IfUnmodifiedSince]; set => this[Header.IfUnmodifiedSince] = value; } /// /// Gets or sets the value of the HTTP Max-Forwards header. /// - public string? MaxForwards { get => base[Header.MaxForwards]; set => base[Header.MaxForwards] = value; } + public string? MaxForwards { get => this[Header.MaxForwards]; set => this[Header.MaxForwards] = value; } /// /// Gets or sets the value of the HTTP Pragma header. /// - public string? Pragma { get => base[Header.Pragma]; set => base[Header.Pragma] = value; } + public string? Pragma { get => this[Header.Pragma]; set => this[Header.Pragma] = value; } /// /// Gets or sets the value of the HTTP Proxy-Authorization header. /// - public string? ProxyAuthorization { get => base[Header.ProxyAuthorization]; set => base[Header.ProxyAuthorization] = value; } + public string? ProxyAuthorization { get => this[Header.ProxyAuthorization]; set => this[Header.ProxyAuthorization] = value; } /// /// Gets or sets the value of the HTTP TE header. /// - public string? TE { get => base[Header.TE]; set => base[Header.TE] = value; } + public string? TE { get => this[Header.TE]; set => this[Header.TE] = value; } /// /// Gets or sets the value of the HTTP Trailer header. /// - public string? Trailer { get => base[Header.Trailer]; set => base[Header.Trailer] = value; } + public string? Trailer { get => this[Header.Trailer]; set => this[Header.Trailer] = value; } /// /// Gets or sets the value of the HTTP Via header. /// - public string? Via { get => base[Header.Via]; set => base[Header.Via] = value; } + public string? Via { get => this[Header.Via]; set => this[Header.Via] = value; } /// /// Gets or sets the value of the HTTP Set-Cookie header. /// - /// - /// Tip: use property to getting cookies values from requests and - /// on to set cookies. - /// - public string? SetCookie { get => base[Header.SetCookie]; set => base[Header.SetCookie] = value; } + public string? SetCookie { get => this[Header.SetCookie]; set => this[Header.SetCookie] = value; } /// /// Gets or sets the value of the HTTP User-Agent header. /// - public string? UserAgent { get => base[Header.UserAgent]; set => base[Header.UserAgent] = value; } + public string? UserAgent { get => this[Header.UserAgent]; set => this[Header.UserAgent] = value; } /// /// Gets or sets the value of the HTTP Vary header. /// - public string? Vary { get => base[Header.Vary]; set => base[Header.Vary] = value; } + public string? Vary { get => this[Header.Vary]; set => this[Header.Vary] = value; } /// /// Gets or sets the value of the HTTP WWW-Authenticate header. /// - public string? WWWAuthenticate { get => base[Header.WWWAuthenticate]; set => base[Header.WWWAuthenticate] = value; } + public string? WWWAuthenticate { get => this[Header.WWWAuthenticate]; set => this[Header.WWWAuthenticate] = value; } /// /// Gets or sets the value of the HTTP X-Forwarded-For header. @@ -303,7 +519,7 @@ public override string ToString() /// /// Tip: enable the property to obtain the user client proxied IP throught . /// - public string? XForwardedFor { get => base[Header.XForwardedFor]; set => base[Header.XForwardedFor] = value; } + public string? XForwardedFor { get => this[Header.XForwardedFor]; set => this[Header.XForwardedFor] = value; } /// /// Gets or sets the value of the HTTP X-Forwarded-Host header. @@ -311,6 +527,46 @@ public override string ToString() /// /// Tip: enable the property to obtain the client requested host throught . /// - public string? XForwardedHost { get => base[Header.XForwardedHost]; set => base[Header.XForwardedHost] = value; } + public string? XForwardedHost { get => this[Header.XForwardedHost]; set => this[Header.XForwardedHost] = value; } + /// + public ICollection Keys + { + get + { + List headerNames = new List(_headers.Count); + for (int i = 0; i < _headers.Count; i++) + { + headerNames.Add(_headers[i].headerName); + } + return headerNames; + } + } + + /// + public ICollection Values + { + get + { + List headerValues = new List(_headers.Count); + for (int i = 0; i < _headers.Count; i++) + { + headerValues.Add(_headers[i].headerValue); + } + return headerValues; + } + } + + /// + public int Count => _headers.Count; + + /// + public bool IsReadOnly => isReadOnly; + #endregion + + void ThrowIfReadOnly() + { + if (isReadOnly) + throw new InvalidOperationException(SR.Collection_ReadOnly); + } } diff --git a/src/Entity/MultipartObject.cs b/src/Entity/MultipartObject.cs index 947957a..1c93b55 100644 --- a/src/Entity/MultipartObject.cs +++ b/src/Entity/MultipartObject.cs @@ -81,7 +81,7 @@ public MultipartObjectCommonFormat GetCommonFileFormat() { Span len8 = ContentBytes.AsSpan(0, 8); - if (len8.SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) + if (len8.SequenceEqual(MultipartObjectCommonFormatByteMark.PNG)) { return MultipartObjectCommonFormat.PNG; } @@ -90,39 +90,40 @@ public MultipartObjectCommonFormat GetCommonFileFormat() { Span len4 = ContentBytes.AsSpan(0, 4); - if (len4.SequenceEqual(new byte[] { (byte)'R', (byte)'I', (byte)'F', (byte)'F' })) + if (len4.SequenceEqual(MultipartObjectCommonFormatByteMark.WEBP)) { return MultipartObjectCommonFormat.WEBP; } - else if (len4.SequenceEqual(new byte[] { 0x25, 0x50, 0x44, 0x46 })) + else if (len4.SequenceEqual(MultipartObjectCommonFormatByteMark.PDF)) { return MultipartObjectCommonFormat.PDF; } + else if (len4.SequenceEqual(MultipartObjectCommonFormatByteMark.TIFF)) + { + return MultipartObjectCommonFormat.TIFF; + } } if (byteLen >= 3) { Span len3 = ContentBytes.AsSpan(0, 3); - if (len3.SequenceEqual(new byte[] { 0xFF, 0xD8, 0xFF })) + if (len3.SequenceEqual(MultipartObjectCommonFormatByteMark.JPEG)) { return MultipartObjectCommonFormat.JPEG; } - else if (len3.SequenceEqual(new byte[] { 73, 73, 42 })) - { - return MultipartObjectCommonFormat.TIFF; - } - else if (len3.SequenceEqual(new byte[] { 77, 77, 42 })) + else if (len3.SequenceEqual(MultipartObjectCommonFormatByteMark.GIF)) { - return MultipartObjectCommonFormat.TIFF; + return MultipartObjectCommonFormat.GIF; } - else if (len3.SequenceEqual(new byte[] { 0x42, 0x4D })) + } + if (byteLen >= 2) + { + Span len2 = ContentBytes.AsSpan(0, 2); + + if (len2.SequenceEqual(MultipartObjectCommonFormatByteMark.BMP)) { return MultipartObjectCommonFormat.BMP; } - else if (len3.SequenceEqual(new byte[] { 0x47, 0x46, 0x49 })) - { - return MultipartObjectCommonFormat.GIF; - } } return MultipartObjectCommonFormat.Unknown; @@ -130,7 +131,7 @@ public MultipartObjectCommonFormat GetCommonFileFormat() internal MultipartObject(NameValueCollection headers, string? filename, string name, byte[]? body, Encoding encoding) { - Headers = new HttpHeaderCollection(headers); + Headers = new HttpHeaderCollection(headers) { isReadOnly = true }; Filename = filename; Name = name; ContentBytes = body ?? Array.Empty(); @@ -314,50 +315,4 @@ bool Equals(byte[] source, byte[] separator, int index) return new MultipartFormCollection(outputObjects); } } - - /// - /// Represents an image format for Multipart objects. - /// - public enum MultipartObjectCommonFormat - { - /// - /// Represents that the object is not a recognized image. - /// - Unknown = 0, - - /// - /// Represents an JPEG/JPG image. - /// - JPEG = 100, - - /// - /// Represents an GIF image. - /// - GIF = 101, - - /// - /// Represents an PNG image. - /// - PNG = 102, - - /// - /// Represents an TIFF image. - /// - TIFF = 103, - - /// - /// Represents an bitmap image. - /// - BMP = 104, - - /// - /// Represents an WebP image. - /// - WEBP = 105, - - /// - /// Represents an PDF file. - /// - PDF = 200 - } } diff --git a/src/Entity/MultipartObjectCommonFormat.cs b/src/Entity/MultipartObjectCommonFormat.cs new file mode 100644 index 0000000..b191652 --- /dev/null +++ b/src/Entity/MultipartObjectCommonFormat.cs @@ -0,0 +1,56 @@ +// 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: MultipartObjectCommonFormat.cs +// Repository: https://github.com/sisk-http/core + +namespace Sisk.Core.Entity; + +/// +/// Represents an image format for Multipart objects. +/// +public enum MultipartObjectCommonFormat +{ + /// + /// Represents that the object is not a recognized image. + /// + Unknown = 0, + + /// + /// Represents an JPEG/JPG image. + /// + JPEG = 100, + + /// + /// Represents an GIF image. + /// + GIF = 101, + + /// + /// Represents an PNG image. + /// + PNG = 102, + + /// + /// Represents an TIFF image. + /// + TIFF = 103, + + /// + /// Represents an bitmap image. + /// + BMP = 104, + + /// + /// Represents an WebP image. + /// + WEBP = 105, + + /// + /// Represents an PDF file. + /// + PDF = 200 +} diff --git a/src/Entity/MultipartObjectCommonFormatByteMark.cs b/src/Entity/MultipartObjectCommonFormatByteMark.cs new file mode 100644 index 0000000..59f2d9e --- /dev/null +++ b/src/Entity/MultipartObjectCommonFormatByteMark.cs @@ -0,0 +1,25 @@ +// 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: MultipartObjectCommonFormatByteMark.cs +// Repository: https://github.com/sisk-http/core + +namespace Sisk.Core.Entity; + +internal static class MultipartObjectCommonFormatByteMark +{ + public static readonly byte[] PNG = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + + public static readonly byte[] PDF = new byte[] { 0x25, 0x50, 0x44, 0x46 }; + public static readonly byte[] WEBP = new byte[] { 0x52, 0x49, 0x46, 0x46 }; + public static readonly byte[] TIFF = new byte[] { 0x4D, 0x4D, 0x00, 0x2A }; + public static readonly byte[] WEBM = new byte[] { 0x1A, 0x45, 0xDF, 0xA3 }; + + public static readonly byte[] JPEG = new byte[] { 0xFF, 0xD8, 0xFF }; + public static readonly byte[] GIF = new byte[] { 0x47, 0x46, 0x49 }; + + public static readonly byte[] BMP = new byte[] { 0x42, 0x4D }; +} diff --git a/src/Entity/StringValueCollection.cs b/src/Entity/StringValueCollection.cs index 573ac23..7247623 100644 --- a/src/Entity/StringValueCollection.cs +++ b/src/Entity/StringValueCollection.cs @@ -74,9 +74,9 @@ public NameValueCollection AsNameValueCollection() { NameValueCollection n = new NameValueCollection(); - foreach (string key in items.Keys) + foreach (var item in items) { - n.Add(key, items[key]); + n.Add(item.Key, item.Value); } return n; diff --git a/src/Http/Handlers/HttpServerHandlerRepository.cs b/src/Http/Handlers/HttpServerHandlerRepository.cs index 9687c4a..8878479 100644 --- a/src/Http/Handlers/HttpServerHandlerRepository.cs +++ b/src/Http/Handlers/HttpServerHandlerRepository.cs @@ -16,7 +16,7 @@ namespace Sisk.Core.Http.Handlers; internal class HttpServerHandlerRepository { private readonly HttpServer parent; - private readonly List handlers = new List(); + private readonly List handlers = new List(20); internal readonly DefaultHttpServerHandler _default = new DefaultHttpServerHandler(); public HttpServerHandlerRepository(HttpServer parent) diff --git a/src/Http/HttpContext.cs b/src/Http/HttpContext.cs index 9046f73..c4774ad 100644 --- a/src/Http/HttpContext.cs +++ b/src/Http/HttpContext.cs @@ -7,6 +7,7 @@ // File name: HttpContext.cs // Repository: https://github.com/sisk-http/core +using Sisk.Core.Entity; using Sisk.Core.Routing; using System.Collections.Specialized; @@ -21,7 +22,7 @@ public sealed class HttpContext /// Gets or sets an indicating HTTP headers which /// will overwrite headers set by CORS, router response or request handlers. /// - public NameValueCollection OverrideHeaders { get; set; } = new NameValueCollection(); + public HttpHeaderCollection OverrideHeaders { get; set; } = new HttpHeaderCollection(); /// /// Gets the instance of this HTTP context. diff --git a/src/Http/HttpRequest.cs b/src/Http/HttpRequest.cs index 1982129..8014034 100644 --- a/src/Http/HttpRequest.cs +++ b/src/Http/HttpRequest.cs @@ -151,8 +151,10 @@ public HttpHeaderCollection Headers } else { - headers = new HttpHeaderCollection(listenerRequest.Headers); + headers = new HttpHeaderCollection((WebHeaderCollection)listenerRequest.Headers); } + + headers.isReadOnly = true; } return headers; @@ -367,10 +369,9 @@ public string GetRawHttpRequest(bool includeBody = true, bool appendExtraInfo = sb.AppendLine($":request-id: {RequestId}"); sb.AppendLine($":request-proto: {(IsSecure ? "https" : "http")}"); } - foreach (string hName in Headers) + foreach (var header in Headers) { - string hValue = Headers[hName]!; - sb.AppendLine($"{hName}: {hValue}"); + sb.AppendLine($"{header.Key}: {header.Value}"); } sb.AppendLine(); diff --git a/src/Http/HttpResponse.cs b/src/Http/HttpResponse.cs index 41928c9..48cedee 100644 --- a/src/Http/HttpResponse.cs +++ b/src/Http/HttpResponse.cs @@ -102,10 +102,9 @@ public string GetRawHttpResponse(bool includeBody = true) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"HTTP/1.1 {StatusInformation}"); - foreach (string header in Headers) + foreach (var header in Headers) { - sb.Append(header + ": "); - sb.Append(Headers[header]); + sb.Append($"{header.Key}: {header.Value}"); sb.Append('\n'); } if (Content?.Headers is not null) @@ -167,7 +166,7 @@ public HttpResponse WithContent(HttpContent content) /// The self object. public HttpResponse WithHeader(string headerKey, string headerValue) { - Headers.Set(headerKey, headerValue); + Headers.Add(headerKey, headerValue); return this; } @@ -179,7 +178,7 @@ public HttpResponse WithHeader(string headerKey, string headerValue) public HttpResponse WithHeader(NameValueCollection headers) { foreach (string key in headers.Keys) - Headers.Set(key, headers[key]); + Headers.Add(key, headers[key]); return this; } @@ -286,7 +285,7 @@ public HttpResponse(HttpStatusCode status, HttpContent? content) /// protected override void SetCookieHeader(String name, String value) { - Headers.Set(name, value); + Headers.Add(name, value); } } } diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs index 19298d5..705eb6f 100644 --- a/src/Http/HttpServer__Core.cs +++ b/src/Http/HttpServer__Core.cs @@ -10,7 +10,6 @@ using Sisk.Core.Entity; using Sisk.Core.Internal; using Sisk.Core.Routing; -using System.Collections.Specialized; using System.Diagnostics; using System.Net; using System.Runtime.CompilerServices; @@ -308,23 +307,25 @@ private void ProcessRequest(HttpListenerContext context) #endregion #region Step 4 - Response computing - NameValueCollection resHeaders = new NameValueCollection - { - response.Headers - }; + HttpHeaderCollection resHeaders = response.Headers; + HttpHeaderCollection overrideHeaders = srContext.OverrideHeaders; long? responseContentLength = response.Content?.Headers.ContentLength; - if (srContext?.OverrideHeaders.Count > 0) resHeaders.Add(srContext.OverrideHeaders); + if (overrideHeaders.Count > 0) + { + for (int i = 0; i < overrideHeaders.Count; i++) + { + var overridingHeader = overrideHeaders[i]; + resHeaders.Set(overridingHeader.Item1, overridingHeader.Item2); + } + } for (int i = 0; i < resHeaders.Count; i++) { - string? incameHeader = resHeaders.Keys[i]; - if (string.IsNullOrEmpty(incameHeader)) continue; - - string? value = resHeaders[incameHeader]; - if (string.IsNullOrEmpty(value)) continue; + (string, string) incameHeader = resHeaders[i]; + if (string.IsNullOrEmpty(incameHeader.Item1)) continue; - baseResponse.Headers[incameHeader] = value; + baseResponse.Headers.Add(incameHeader.Item1, incameHeader.Item2); } _debugState = "sent_headers"; diff --git a/src/Internal/LoggingConstants.cs b/src/Internal/LoggingConstants.cs index 566b191..3d8309a 100644 --- a/src/Internal/LoggingConstants.cs +++ b/src/Internal/LoggingConstants.cs @@ -22,7 +22,7 @@ internal class LoggingFormatter readonly DateTime d; readonly Uri? bReqUri; readonly IPAddress? bReqIpAddr; - readonly NameValueCollection? reqHeaders; + readonly IDictionary? reqHeaders; readonly int bResStatusCode; readonly string? bResStatusDescr; readonly string bReqMethod; @@ -34,7 +34,7 @@ public LoggingFormatter( HttpServerExecutionResult res, DateTime d, Uri? bReqUri, IPAddress? bReqIpAddr, - NameValueCollection? reqHeaders, + IDictionary? reqHeaders, int bResStatusCode, string? bResStatusDescr, long execTime, diff --git a/src/Internal/SR.cs b/src/Internal/SR.cs index 93bda01..e908585 100644 --- a/src/Internal/SR.cs +++ b/src/Internal/SR.cs @@ -89,6 +89,8 @@ static partial class SR public const string ValueItem_ValueNull = "The value \"{0}\" contained at this {1} is null or it's undefined."; public const string ValueItem_CastException = "Cannot cast the value \"{0}\" at parameter {1} into an {2}."; + public const string Collection_ReadOnly = "Cannot insert items to this collection as it is read-only."; + public static string Format(string format, params object?[] items) { return String.Format(format, items); diff --git a/src/Sisk.Core.csproj b/src/Sisk.Core.csproj index 571d861..ee5b789 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-rc4 + 1.0.0.0-rc5 LICENSE.txt False