Files
CoreUtils/StreamReader.h
T
Brad ArantandClaude Sonnet 5 a033cc6718 Add StreamReader and rebuild Parser/LineParser/IMFHeader/IMFBoundary on it
Introduces StreamReader to accumulate socket data delivered in arbitrary
chunks into one buffer, so a Parser can build an object (a line, a header
block, a MIME boundary) that spans any number of packets without losing
its place. Parser is redesigned around this: no longer inherits MString
(was redundant with its own buffer member), takes a StreamReader&, and
drives a resumable COMPLETE/INCOMPLETE/DATA_AVAIL state-machine protocol
via doParse(). LineParser, IMFHeader, and IMFBoundary are rewritten as
real, working resumable parsers against the shared buffer instead of the
previous non-compiling stubs (mismatched constructors/return types, a
syntax error, checkParse() never implemented).

IMFFormData is left as the stub it already was, just restored to
compiling against the new IMFHeader signature - fully wiring it up needs
IMFMultipart/IMFText migrated to StreamReader too, out of scope here.

Verified with real runtime tests (not just compilation) covering data
split mid-line, mid-CRLF, mid-header-fold, and mid-boundary-marker across
separate feed() calls, plus StreamReader::compact(). Along the way fixed
a header-value bug (space after ':' wasn't skipped) and a double
cursor-offset subtraction bug in two places caused by forgetting that
ZString::getLength() is already cursor-relative, not the raw buffer size.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 14:25:58 -07:00

67 lines
1.9 KiB
C++

#ifndef __StreamReader_h__
#define __StreamReader_h__
#include "MString.h"
namespace coreutils {
///
/// StreamReader accumulates data delivered in arbitrary-sized chunks (e.g. from
/// core::Socket::onDataReceived) into one continuous, growable buffer, and exposes
/// it to a chain of Parser objects so they can build higher-level protocol objects
/// (a header, a line, a MIME part, etc.) without needing to worry about where one
/// network packet ended and the next began - a "complete" unit may span any number
/// of feed() calls.
///
/// Parsers consume data through the ZString-style cursor on getBuffer(). Once a
/// Parser has fully consumed a unit (advanced the cursor past it), call compact()
/// to drop the already-parsed prefix so the buffer doesn't grow unboundedly for the
/// life of a long-running connection.
///
class StreamReader {
public:
StreamReader();
///
/// Append newly-arrived data to the end of the buffer. Does not affect the
/// cursor - existing parse position is preserved across a feed().
///
void feed(ZString &data);
///
/// The accumulated buffer Parsers read from and advance the cursor over.
///
MString &getBuffer();
///
/// Drop the already-parsed prefix (everything before the cursor) and reset
/// the cursor to the new start of the buffer. Call this once a Parser has
/// completed a unit and before waiting for more data, so the buffer only
/// ever grows by the amount of not-yet-parsed data.
///
void compact();
///
/// True if there is unparsed data currently sitting in the buffer that a
/// Parser hasn't consumed yet (i.e. more progress may be possible without
/// waiting for the next feed()).
///
bool hasUnparsedData();
private:
MString buffer;
};
}
#endif