Files
CoreUtils/IMFHeader.cpp
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

78 lines
2.3 KiB
C++

#include "IMFHeader.h"
#include "Exception.h"
namespace coreutils {
IMFHeader::IMFHeader(StreamReader &reader) : Parser(reader) {}
Parser::Status IMFHeader::parse() {
MString &buffer = reader.getBuffer();
switch (state) {
case NAME: {
if (buffer.eod())
return Status::INCOMPLETE;
if (buffer.getChar() == '\r') {
// ZString::getLength() is already cursor-relative.
if (buffer.getLength() < 2)
return Status::INCOMPLETE;
if (!buffer.ifNext(ZString("\r\n")))
throw coreutils::Exception("IMFHeader: expected CRLF at end of header block.");
return Status::COMPLETE;
}
name << buffer.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789");
if (buffer.eod())
return Status::INCOMPLETE;
if (!buffer.ifNext(ZString(":")))
throw coreutils::Exception("IMFHeader: expected ':' in header name.");
state = SKIP_SPACE;
return Status::DATA_AVAIL;
}
case SKIP_SPACE:
while (!buffer.eod() && buffer.getChar() == ' ')
buffer.nextChar();
if (buffer.eod())
return Status::INCOMPLETE;
state = FIELD;
return Status::DATA_AVAIL;
case FIELD:
field << buffer.getTokenExclude("\r\n");
if (buffer.eod())
return Status::INCOMPLETE;
if (!buffer.ifNext(ZString("\r\n")))
throw coreutils::Exception("IMFHeader: expected CRLF at end of header field.");
state = FOLD_CHECK;
return Status::DATA_AVAIL;
case FOLD_CHECK:
if (buffer.eod())
return Status::INCOMPLETE;
if (buffer.getChar() == ' ' || buffer.getChar() == '\t') {
buffer.nextChar();
field << " ";
state = SKIP_SPACE;
return Status::DATA_AVAIL;
}
commitField();
state = NAME;
return Status::DATA_AVAIL;
}
throw coreutils::Exception("IMFHeader: unreachable state.");
}
void IMFHeader::commitField() {
headers[name] = field;
name = MString();
field = MString();
}
std::map<MString, MString> &IMFHeader::getHeaders() {
return headers;
}
}