Repository navigation
Version 5 release - #19
Merged
Merged
Conversation
Drop net6.0, add net10.0 as the new LTS, keep net8.0 as the LTS floor. Also drop the explicit System.Text.Json 6.0.10 package reference: it only existed for net6.0's benefit and NuGet flags it as redundant (NU1510) now that both remaining TFMs ship a newer System.Text.Json in-box.
…n StreamPeaker DualSecureWebServer.StreamPeaker peeks one byte to decide whether an incoming connection is TLS or plaintext. GetFirstByte() discarded the result of the underlying Read(), so a connection that closed before sending anything looked identical to one whose first byte genuinely is 0x00 - both left the peek buffer at its default value. The new net10.0 analyzer set flags the discarded read (CA2022), which is what surfaced this. Track end-of-stream explicitly via HasFirstByte/baseStreamAtEnd instead of overloading the peeked byte itself. Also relax the constructor from NetworkStream to Stream (no NetworkStream-specific members were used), and make the class internal with an InternalsVisibleTo to the test project so this can be covered directly instead of only through a live socket.
Test project now multi-targets net10.0;net8.0 to match the library. While on net10.0, NuGet's package audit flagged the 2022-era MSTest.TestAdapter/MSTest.TestFramework 2.2.10 for pulling in a Newtonsoft.Json version with a known high-severity advisory; bumped Microsoft.NET.Test.Sdk, MSTest.TestAdapter, MSTest.TestFramework and coverlet.collector to their current major versions. All 52 existing tests plus the new StreamPeaker tests pass unchanged on both TFMs, so no test-source changes were needed for that jump. The example projects don't need to multi-target; moved them straight to net10.0.
Matches the library's new net10.0;net8.0 target list. main.yml now installs 10.0/8.0 instead of 8.0/6.0.102; nuget.yml's pack job only needs the 10.0 SDK since a newer SDK can still build older TFMs.
…ges (CA1510, CA1512, CA1513, CA2208)
…cumented (CA2000)
…ing (CA1307, CA1310, CA1309, CA1866, CA1834, CA1820)
…lity (CA1304, CA1862, CA1311)
…locking calls with their async counterparts (CA2007, CA1849)
…ction lookups (CA1861, CA1825, CA1864, CA1868)
…m overloads (CA1822, CA1844, CA1835)
…rals (IDE0300, IDE0301)
…time (SYSLIB1045)
… reads Reading long HTTP header blocks (issue #18) could silently truncate header lines and inject garbage bytes. Two bugs in NetworkReader caused this: - Every time a line ended in "\r\n", the reader advanced past the '\n' but never decremented charBufferCount to match. This drift builds up by one for every CRLF-terminated line, and once it accumulates enough it pushes the line scan past the actually-decoded data into stale leftover characters from a previous, unrelated decode pass (the char buffer is reused in place, never cleared). A stray '\r'/'\n' left over there is then misread as a line terminator, truncating the real line mid-value and appending unrelated trailing bytes. More header lines means more accumulated drift, which is why this only showed up with larger header blocks. - RefillBufferAsync skipped compacting the byte buffer back to offset 0 once it was fully drained exactly at the buffer's end (a stray "readBufferCount > 0" guard). The next read then targeted a zero-length slice, returned 0, and was misread as the stream being closed, even though more data was still available on the socket. Added regression tests reproducing both failure modes directly against NetworkReader. Close #18
Covers CRLF boundary handling (CR/LF split across a refill, bare CR without LF, consecutive blank lines), adversarial encoding (invalid UTF-8 bytes, a multi-byte character dribbled one byte per read), and degenerate input (empty stream, ReadUntil marking never found, empty marking).
Adds tests for the previously-untested sync API (ReadLine/ReadChar/ PeekChar and their RefillBuffer helpers), Dispose/DisposeAsync lifecycle, constructor validation, the raw byte-read overloads (Read, ReadAsync(Memory<byte>), ReadMemoryAsync), sync ReadUntil, the Encoding setter, and ReadLineAsync's limit/overflow branches.
Drop WebServerLog's bespoke ServerLogItem/ServerLogType/events in favor of the standard ILogger abstraction, so any third-party logging backend (Serilog, NLog, log4net, ...) binds via its own ILoggerFactory setup with no MaxLib-specific glue code. WebServerLog now only exposes a write-once SetLoggerFactory(...); every class resolves a static per-type ILogger (or ILogger keyed by GetType() on the WebSocket abstract extension points) against it, with real Exception objects passed through instead of being stringified.
Both were marked [Obsolete] and had zero references outside their own cluster. The Api/Api.Rest REST endpoint system is fully superseded by the Builder system; StandardDocumentLoader had no callers left at all. Their dedicated tests (TestRestActionEndpoint, TestRestApiFactory) are removed too - Builder binds parameters via declarative attributes, so there's no equivalent shape to port them to.
The read chunk size was never recomputed, so a Range request for a few bytes read the whole file from disk. The chunk size now follows the remaining count and the loop stops once it is satisfied.
Malformed JSON became a null value instead of a 400, a result that serialized to null always failed, and parsed documents and writers were left to the GC. Parse errors now propagate, null is written once, and both are disposed.
A malformed parameter made the debugger's /api/route endpoint throw instead of returning its routing report. The dry run now treats a conversion failure as an unresolved parameter.
In Prefix mode a URL shorter than the template still matched, so a route group behind a multi-segment prefix also answered truncated URLs. The length check now applies in Prefix mode too, also in the debugger.
Enum parameters went through Convert.ChangeType, which always throws, so such endpoints answered every request with 400. They now use Enum.Parse, case-insensitively, and reject numbers outside the defined members.
RotateSessionKey left the old id valid, and an expired but unswept id was reused for a new session. Old keys are now removed via the new abstract SessionServiceBase.Remove, expired keys count as free, and Session.Data is a ConcurrentDictionary (breaking for custom stores).
With a MaxAge above about 49 days, rescheduling the sweep timer threw on the timer thread and crashed the process. The delay is now capped at 45 days.
Frames were masked with an all-zero key, defeating RFC 6455 masking for clients built on this API. ApplyMask now generates a fresh random key for every frame.
Reserved opcodes, misplaced Continuation frames and oversized single frames were accepted, and concurrent Close sends could both be written. Violations now close with ProtocolError, MaxMessageSize is checked before allocating, and the close flag is rechecked under the send lock.
An endpoint requiring a subprotocol was reachable by clients requesting none, and one whose protocol name had uppercase letters could never be selected. Matching is now symmetric and case-insensitive.
EventFactory.Parse rejected valid JSON whenever "$type" was not the first property. It now resolves the type with an order-independent pre-scan, which also works with UnmappedMemberHandling.Disallow.
Add doc comments on what callers and custom implementations must not do: concurrent Dispose, registering an event type twice, throwing from ToString(), Message, ReceivedFrame or ReceiveClose, and relying on Cookie.Path being escaped. No behavior changes.
Add XML doc notes to: PathAttribute, Service, ParamAttributeBase, WebServiceCollection, WebServiceGroup, HttpRequestParser, HttpPost, UnknownPostData, MemorySessionService, WebSocketConnection and EventConnection.
This was
linked to
issues
Oct 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains bundled changes for the latest Version 5.0 release.
Close: #7, #8, #9, #10, #11, #12, #13, #15, #18