#include "CGIFormData.h" #include "CGIFormattedData.h" #include "Exception.h" namespace coreutils { CGIFormData::CGIFormData(StreamReader &reader, ZString contentType, int contentLength, ZString boundary) : Parser(reader), contentType(contentType), contentLength(contentLength), boundary(boundary) { if (contentType.startsWith("application/x-www-form-urlencoded")) mode = URLENCODED; else if (contentType.startsWith("multipart/form-data")) mode = MULTIPART; else mode = UNKNOWN; } CGIFormData::~CGIFormData() { delete headerParser; delete boundaryParser; } void CGIFormData::extractPartName(IMFHeader &header) { auto &fields = header.getHeaders(); MString disposition = fields[MString("Content-Disposition")]; ZString dz(disposition.getData(), disposition.getLength()); currentSkip = (dz.find(ZString("filename=\"")) != -1); int namePos = dz.find(ZString("name=\"")); if (namePos != -1) { ZString afterName = dz.substring(namePos + 6); int nameEnd = afterName.find(ZString("\"")); if (nameEnd != -1) currentName = MString(afterName.substring(0, nameEnd)); } } Parser::Status CGIFormData::parse() { MString &buffer = reader.getBuffer(); if (mode == UNKNOWN) { state = DONE; return Status::COMPLETE; } switch (state) { case INIT: if (mode == URLENCODED) { state = URLENCODED_BODY; return Status::DATA_AVAIL; } // MULTIPART: the very first boundary line has no leading CRLF // (RFC 2046) - handled here directly rather than via IMFBoundary, // which always expects the CRLF that terminates a PRECEDING part. // Split into two states (find+consume the marker, then separately // disambiguate -- vs CRLF) so that if the marker is consumed but // there isn't yet enough data to tell -- from CRLF, retrying does // not re-search for (and skip past) a marker that is already // behind the cursor - the exact bug class already fixed once in // IMFBoundary's PREFIX/TAIL split. { MString marker; marker << "--"; marker << boundary; int pos = buffer.find(marker); if (pos < 0) return Status::INCOMPLETE; if (!buffer.ifNext(marker)) throw coreutils::Exception("CGIFormData: opening boundary vanished between find and ifNext."); state = OPENING_TAIL; return Status::DATA_AVAIL; } case OPENING_TAIL: if (buffer.getLength() < 2) return Status::INCOMPLETE; if (buffer.ifNext(ZString("--"))) { state = DONE; return Status::COMPLETE; } if (!buffer.ifNext(ZString("\r\n"))) throw coreutils::Exception("CGIFormData: malformed opening boundary line."); state = PART_HEADERS; return Status::DATA_AVAIL; case URLENCODED_BODY: if ((int)buffer.getLength() < contentLength) return Status::INCOMPLETE; { ZString body(buffer.getCursor(), contentLength); buffer.setCursor(buffer.getCursor() + contentLength); CGIFormattedData parsed(body); for (auto &pair : parsed.data) storeValue(pair.first, pair.second); } state = DONE; return Status::COMPLETE; case PART_HEADERS: if (!headerParser) headerParser = new IMFHeader(reader); if (!checkParse(*headerParser)) return Status::INCOMPLETE; extractPartName(*headerParser); delete headerParser; headerParser = NULL; state = PART_BODY; return Status::DATA_AVAIL; case PART_BODY: { MString marker; marker << "\r\n--"; marker << boundary; char *bodyStart = buffer.getCursor(); int pos = buffer.find(marker); if (pos < 0) return Status::INCOMPLETE; char *bodyEnd = buffer.getCursor(); if (!currentSkip && currentName.getLength() > 0) storeValue(currentName, MString(ZString(bodyStart, bodyEnd - bodyStart))); currentName = MString(); currentSkip = false; state = PART_BOUNDARY; return Status::DATA_AVAIL; } case PART_BOUNDARY: if (!boundaryParser) boundaryParser = new IMFBoundary(reader, boundary); if (!checkParse(*boundaryParser)) return Status::INCOMPLETE; { bool last = boundaryParser->isLast(); delete boundaryParser; boundaryParser = NULL; if (last) { state = DONE; return Status::COMPLETE; } state = PART_HEADERS; return Status::DATA_AVAIL; } case DONE: return Status::COMPLETE; } throw coreutils::Exception("CGIFormData: unreachable state."); } void CGIFormData::storeValue(MString name, MString value) { int index = nameCounts[name]++; MString key = name; key << ":"; key << MString(std::to_string(index)); data[key] = value; } MString CGIFormData::operator[](ZString name) { MString key(name); if (key.find(ZString(":")) == -1) { key << ":0"; } return data[key]; } }