Skip to content

Version 5 release - #19

Merged
Garados007 merged 139 commits into
mainfrom
version-5
Oct 3, 2026
Merged

Garados007 merged 139 commits into
mainfrom
version-5

Conversation

@Garados007

Copy link
Copy Markdown
Owner

This PR contains bundled changes for the latest Version 5.0 release.

Close: #7, #8, #9, #10, #11, #12, #13, #15, #18

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.
…ing (CA1307, CA1310, CA1309, CA1866, CA1834, CA1820)
…locking calls with their async counterparts (CA2007, CA1849)
…ction lookups (CA1861, CA1825, CA1864, CA1868)
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment