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>
This commit is contained in:
1 parent
94d4a30e88
commit
a033cc6718
13 files changed
+443
-136
No files matched your search
+67
-22
@@ -1,32 +1,77 @@
|
||||
#include "IMFHeader.h"
|
||||
#include "Exception.h"
|
||||
#include "Log.h"
|
||||
#include <unistd.h>
|
||||
|
||||
namespace coreutils {
|
||||
|
||||
IMFHeader::IMFHeader(int fd) {}
|
||||
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;
|
||||
|
||||
virtual int parse() {
|
||||
switch(state) {
|
||||
case NAME:
|
||||
name << buffer.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789");
|
||||
if(buffer.ifNext(":"))
|
||||
state = FIELD;
|
||||
else
|
||||
return 2;
|
||||
case FIELD:
|
||||
field << buffer.getTokenInclude("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-;'\"0123456789");
|
||||
if(!buffer.ifNext("\r\n"))
|
||||
if(buffer.eod())
|
||||
return 2;
|
||||
// Right here is where we should check if next line is an extension to the previous line.
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user