#include "HTTPRequest.h" #include "Exception.h" #include namespace coreutils { HTTPRequest::HTTPRequest(StreamReader &reader) : Parser(reader) {} HTTPRequest::~HTTPRequest() { delete lineParser; delete headerParser; delete formParser; } Parser::Status HTTPRequest::parse() { MString &buffer = reader.getBuffer(); switch (state) { case REQUEST_LINE: { if (!lineParser) lineParser = new LineParser(reader); if (!checkParse(*lineParser)) return Status::INCOMPLETE; { ZString line = lineParser->getLine(); ZString sp(" "); std::vector &parts = line.split(sp); if (parts.size() != 3) throw coreutils::Exception("HTTPRequest: malformed request line " "(expected \"METHOD /path HTTP/1.1\")."); method = MString(parts[0]); path = MString(parts[1]); version = MString(parts[2]); } delete lineParser; lineParser = NULL; { // Split the query string off before anything else sees `path` - // a route table (see the APIServer plan) should never have to // deal with a trailing "?a=1&b=2" itself. int qpos = path.find(ZString("?")); if (qpos != -1) { queryString = MString(path.substring(qpos + 1)); path = MString(path.substring(0, qpos)); } } state = HEADERS; return Status::DATA_AVAIL; } case HEADERS: if (!headerParser) headerParser = new IMFHeader(reader); if (!checkParse(*headerParser)) return Status::INCOMPLETE; headers = headerParser->getHeaders(); delete headerParser; headerParser = NULL; state = BODY; return Status::DATA_AVAIL; case BODY: { MString contentLengthHeader = getHeader("Content-Length"); if (contentLengthHeader.getLength() == 0) { if (getHeader("Transfer-Encoding").getLength() > 0) throw coreutils::Exception("HTTPRequest: chunked Transfer-Encoding " "is not supported (Content-Length only)."); state = DONE; return Status::COMPLETE; } int contentLength = contentLengthHeader.asInteger(); MString contentType = getHeader("Content-Type"); if (contentType.startsWith("application/x-www-form-urlencoded") || contentType.startsWith("multipart/form-data")) { if (!formParser) { // Mirrors __jet.cpp's identical CGI-path fix: find() leaves // contentType's own cursor at the match, and CGIFormData's // constructor copy-constructs its own contentType member // from whatever we pass, inheriting that same moved cursor // - its startsWith() dispatch checks are cursor-relative // and would silently miss (falling through to UNKNOWN) // without the reset(). int boundaryPos = contentType.find(ZString("boundary=")); if (boundaryPos != -1) boundary = MString(contentType.substring(boundaryPos + 9)); contentType.reset(); formParser = new CGIFormData(reader, contentType, contentLength, boundary); } if (!checkParse(*formParser)) return Status::INCOMPLETE; state = DONE; return Status::COMPLETE; } if ((int)buffer.getLength() < contentLength) return Status::INCOMPLETE; body = MString(ZString(buffer.getCursor(), contentLength)); buffer.setCursor(buffer.getCursor() + contentLength); state = DONE; return Status::COMPLETE; } case DONE: return Status::COMPLETE; } throw coreutils::Exception("HTTPRequest: unreachable state."); } MString HTTPRequest::getMethod() { return method; } MString HTTPRequest::getPath() { return path; } MString HTTPRequest::getQueryString() { return queryString; } MString HTTPRequest::getVersion() { return version; } MString HTTPRequest::getBody() { return body; } std::map &HTTPRequest::getHeaders() { return headers; } CGIFormData *HTTPRequest::getFormData() { return formParser; } MString HTTPRequest::getHeader(const char *name) { for (auto &pair : headers) { MString key = pair.first; if (strcasecmp(key.c_str(), name) == 0) return pair.second; } return MString(); } }