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>
67 lines
1.9 KiB
C++
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
|