Files
CoreUtils/CGIFormData.h
T
Brad Arant 78c45d378c Fix File stat-before-open crash risk, JSON string-quoting bug, and MString buffer overflow
- File: stat() only after a successful open() (previously ran first with
  its result ignored, feeding a missing file's garbage st_size into a
  realloc - could crash the whole APIServer on a request for a
  nonexistent static asset)
- JString::setString(): always quote+escape the value instead of going
  through set()'s literal-value guess, which misclassified any string
  starting with digits (e.g. a UUID) as a bare numeric literal and wrote
  it unquoted, corrupting later edits to the same JSON document
- MString::remove() (both overloads): fixed an off-by-one that read one
  byte past the buffer on a completely full string (found via ASan under
  concurrent load)
- CGIFormData: added fields() so callers can iterate every parsed field
  without knowing names in advance (used to merge POST body into
  jet::Global::variables the same way query-string params already are)
2026-09-03 05:14:24 +00:00

84 lines
2.9 KiB
C++

#ifndef __CGIFormData_h__
# define __CGIFormData_h__
#include "Parser.h"
#include "IMFHeader.h"
#include "IMFBoundary.h"
#include <map>
namespace coreutils {
///
/// CGIFormData parses a CGI request body (application/x-www-form-urlencoded
/// or multipart/form-data) from the shared StreamReader and exposes the
/// resulting name/value pairs via operator[] - the object a caller (e.g.
/// JetCore's Tag::getVariable, for its $[:name] CGI variable syntax) holds
/// onto and indexes by variable name once doParse() reports COMPLETE.
///
/// Resumable like every other Parser here: for multipart/form-data, each
/// part's boundary line and header block are parsed via IMFBoundary/
/// IMFHeader (chained through checkParse(), sharing this same
/// StreamReader), so a part - or even its boundary marker - can straddle
/// any number of feed() calls without losing state. File-upload parts
/// (Content-Disposition with a filename= parameter) are recognized and
/// skipped - matching this codebase's existing convention that uploads
/// get a separate mechanism, not buffered through here.
///
class CGIFormData : public Parser {
public:
CGIFormData(StreamReader &reader, ZString contentType, int contentLength, ZString boundary);
virtual ~CGIFormData();
MString operator[](ZString name);
// Every parsed field, keyed as "name:N" (see storeValue's own doc
// comment below) - lets a caller (ApiSession::handleComplete, to
// merge POST body fields into jet::Global::variables the same way
// path params/query string already are - see its own doc comment)
// iterate every field without knowing its name in advance, which
// operator[] alone doesn't support.
const std::map<MString, MString> &fields() const { return data; }
protected:
Status parse() override;
private:
enum Mode { URLENCODED, MULTIPART, UNKNOWN } mode;
enum State { INIT, OPENING_TAIL, PART_HEADERS, PART_BODY, PART_BOUNDARY, URLENCODED_BODY, DONE } state = INIT;
ZString contentType;
int contentLength;
ZString boundary;
std::map<MString, MString> data;
IMFHeader *headerParser = NULL;
IMFBoundary *boundaryParser = NULL;
MString currentName;
bool currentSkip = false;
std::map<MString, int> nameCounts;
void extractPartName(IMFHeader &header);
///
/// Stores value under "name:N", where N is how many values have
/// already been stored under this same base name (0 for the first,
/// 1 for the second, etc.) - matches the indexed key format
/// JetCore's Tag::getVariable's $[:name] syntax looks up by
/// (defaulting to :0 when the caller doesn't specify an index),
/// supporting repeated field names (checkboxes, multi-select) the
/// same way.
///
void storeValue(MString name, MString value);
};
}
#endif