File-upload parts (Content-Disposition with filename=) were unconditionally flagged currentSkip and discarded at PART_BOUNDARY, so every <field type="file"> upload site (track audio, presets, project icons) only ever received an empty value via $[fieldname], never the real uploaded bytes.
83 lines
2.9 KiB
C++
83 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;
|
|
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
|