host/cxpslib/protocolhandler.h (1,152 lines of code) (raw):

/// /// \file protocolhandler.h /// /// \brief for processing requests based on the protocol being used /// #ifndef PROTOCOLHANDLER_H #define PROTOCOLHANDLER_H #include <string> #include <map> #include <set> #include <cstddef> #include <sstream> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/trim.hpp> #include "tagvalue.h" #include "responsecode.h" #include "errorexception.h" #include "compressmode.h" #include "urlencoding.h" #include "cxtransportdefines.h" #include "cxps.h" #include "cxpsheaders.h" #include "Telemetry/TelemetrySharedParams.h" static int const EQUAL_LEN(1); static int const AMPERSAND_LEN(1); static int const MOREDATA_LEN(1); static int const CREATEDIRS_LEN(1); // version #define HTTP_VERSION "HTTP/1.1" ///< http version being used (used for all requests) // methods #define HTTP_METHOD_GET "GET" ///< http get method (used for all requests except putfile) #define HTTP_METHOD_POST "POST" ///< http post method (putfile) // requests #define HTTP_REQUEST_LOGIN "/login" ///< http login request #define HTTP_REQUEST_CFSLOGIN "/cfslogin" ///< http cfs login request #define HTTP_REQUEST_FXLOGIN "/fxlogin" ///< http fx login request #define HTTP_REQUEST_LOGOUT "/logout" ///< http logout request #define HTTP_REQUEST_PUTFILE "/putfile" ///< http put file request #define HTTP_REQUEST_GETFILE "/getfile" ///< http get file request #define HTTP_REQUEST_RENAMEFILE "/renamefile" ///< http rename file request #define HTTP_REQUEST_DELETEFILE "/deletefile" ///< http delete file request #define HTTP_REQUEST_LISTFILE "/listfile" ///< http list file request #define HTTP_REQUEST_HEARTBEAT "/heartbeat" ///< http heartbeat request #define HTTP_REQUEST_CFSHEARTBEAT "/cfsheartbeat" ///< http heartbeat request for cfs control connection #define HTTP_REQUEST_CFSCONNECT "/cfsconnect" ///< http cfs connect request #define HTTP_REQUEST_CFSCONNECTBACK "/cfsconnectback" ///< http cfs connect back request // cs actions need to keep these in sync with admin/web/ScoutAPI/csapijson.php // as these are sent to the cs #define CS_ACTION_GET_CONNECT_INFO "getcfsconnectinfo" #define CS_ACTION_CS_LOGIN "cslogin" #define CS_ACTION_CFS_HEARTBEAT "cfsheartbeat" #define CS_ACTION_CFS_ERROR "cfserror" // param tags #define HTTP_PARAM_TAG_ID "id" ///< http tag for id=<digest> #define HTTP_PARAM_TAG_REQ_ID "reqid" ///< http tag for reqid=<digest> #define HTTP_PARAM_TAG_NAME "name" ///< http tag for name=<filename> #define HTTP_PARAM_TAG_OLDNAME "oldname" ///< http tag for oldname=<name> #define HTTP_PARAM_TAG_NEWNAME "newname" ///< http tag for newame=<name> #define HTTP_PARAM_TAG_CLIENT_NONCE "nonce" ///< http tag for nonce=<nonce> #define HTTP_PARAM_TAG_HOST "host" ///< http tag for host=<hostid> #define HTTP_PARAM_TAG_SERVER_NONCE "cnonce" ///< http tag for cnonce=<nonce> should be snonce but typo and need to keep it cnonce to prevent breakage with older clients #define HTTP_PARAM_TAG_SESSIONID "sessionid" ///< http tag for sessioid=<id> #define HTTP_PARAM_TAG_MORE_DATA "moredata" ///< http tag for moredata=<0|1> (0: no 1: yes) #define HTTP_PARAM_TAG_OFFSET "offset" ///< http tag for offset=<offset> #define HTTP_PARAM_TAG_DATA "data" ///< http tag for data=<data> #define HTTP_PARAM_TAG_COMPRESS_MODE "compress" ///< http tag for data=<data> #define HTTP_PARAM_TAG_CREATE_DIRS "createdirs" ///< http tag for createdirs=<0|1> #define HTTP_PARAM_TAG_FINAL_PATHS "finalpaths" ///< http tag for finalpaths=<path[;path]*> #define HTTP_PARAM_TAG_FILESPEC "filespec" ///< http tag for filespce=<filespce> or filespece=<filespec[;filespec]*> depending on request #define HTTP_PARAM_TAG_MODE "mode" ///< http tag for mode=<mode> #define HTTP_PARAM_TAG_SECURE "secure" ///< http tag for secure=<yes|no> #define HTTP_PARAM_TAG_CXPSIPADDRESS "cxpsip" ///< http tag for cxpsip=<cxpsipaddress> #define HTTP_PARAM_TAG_CFSIPADDRESS "cfsip" ///< http tag for cfsip=<cfsipaddress> #define HTTP_PARAM_TAG_CFSPORT "cfsport" ///< http tag for cfsport=<cfsport> #define HTTP_PARAM_TAG_PROCESSID "pip" ///< http tag for pip=<agent process id> #define HTTP_PARAM_TAG_CS_ACTION "action" ///< http tag for action=<command to execute> #define HTTP_PARAM_TAG_VERSION "ver" ///< http tag for ver=<version> of the api being used #define HTTP_PARAM_TAG_TAG "tag" ///< http tag for tag=<tag> of the api being used #define HTTP_PARAM_TAG_MSG "msg" ///< http tag for msg=<message> of the api being used #define HTTP_PARAM_TAG_APPENDMTIMEUTC "appendmtimeutc" ///< http tag for appendmtimeutc=<0|1> (0: no 1: yes) // headers #define HTTP_HEADER_HOST "Host: " #define HTTP_HEADER_CONTENT_LENGTH "Content-Length: " #define HTTP_HEADER_CONTENT_TYPE "Content-Type: " #define HTTP_HEADER_CONNECTION_KEEP_ALIVE "Connection: Keep-Alive" #define HTTP_HEADER_CONNECTION_CLOSE "Connection: close" #define HTTP_HEADER_ACCEPT "Accept: " #define HTTP_HEADER_USER_AGENT_CXPS "User-Agent: cxps"; // mime types #define HTTP_MIME_JSON "application/json" // end of line #define HTTP_EOL "\r\n" std::string const HTTP_CURRENT_VER_TAG_VALUE(std::string("&ver=") + REQUEST_VER_CURRENT); std::string const HTTP_CURRENT_CS_API_VER_TAG_VALUE(std::string("&ver=") + CS_API_VER_CURRENT); /// \brief protocol handler for parsing a HTTP requests/responses class HttpProtocolHandler { public: /// \brief holds HTTP headers typedef std::map<std::string, std::string> httpHeaders_t; /// \brief protocol handler results enum protocolResult { PROTOCOL_ERROR, ///< protocol violation PROTOCOL_NEED_MORE_DATA, ///< need to read more data PROTOCOL_HAVE_REQUEST, ///< request info read, but still data pending to be read PROTOCOL_COMPLETE, ///< no request info nor data remaining to be read PROTOCOL_EOF ///< eof }; /// \brief to indicate which side the protocol handler is being used enum protocolHandlerSide { CLIENT_SIDE, SERVER_SIDE }; explicit HttpProtocolHandler(protocolHandlerSide handlerSide) ///< indicates which side is being processed client or server : m_handlerSide(handlerSide), m_state(CLIENT_SIDE == handlerSide ? HTTP_READING_RESPONSE_VERSION : HTTP_READING_METHOD), m_contentLength(0), m_haveCrExpectingLf(false), m_responseCode(0) { // TODO: add support for method HEAD m_supportedMethods.insert(HTTP_METHOD_GET); m_supportedMethods.insert(HTTP_METHOD_POST); } ~HttpProtocolHandler() { } /// \brief reset internal state void reset() { m_state = (CLIENT_SIDE == handlerSide() ? HTTP_READING_RESPONSE_VERSION : HTTP_READING_METHOD); m_contentLength = 0; m_haveCrExpectingLf = false; m_tag.clear(); m_value.clear(); m_responseReason.clear(); m_responseCodeAsString.clear(); m_responseCode = 0; m_method.clear(); m_uri.clear(); m_uriParameters.clear(); m_version.clear(); m_headers.clear(); } protocolHandlerSide handlerSide() { return m_handlerSide; } void setHandlerSide(protocolHandlerSide side) { m_handlerSide = side; reset(); } /// \brief process eof /// /// \return /// \li \c PROTOCOL_COMPLETE: if the complete request (header and data) have been read /// \li \c PROTCOL_EOF: reached eof before getting the complete request /// /// \throw ERROR_EXCPTION on error protocolResult processEof() { switch (m_state) { case HTTP_DONE: return PROTOCOL_COMPLETE; case HTTP_READING_METHOD: return PROTOCOL_EOF; default: { throw ERROR_EXCEPTION << "protocol error eof while expecting more data in state: " << stateToString(); } } return PROTOCOL_ERROR; // should not get here } /// \brief process the request /// /// \return /// \li \c PROTOCOL_HAVE_REQUEST: if the request, but not the data have been read /// \li \c PROTOCOL_COMPLETE: if the complete request (header and data) have been read /// \li \c PROTOCOL_MORE_DATA: if more data is needed to process the request /// \li \c PROTCOL_EOF: reached eof before getting the complete request /// /// \throw ERROR_EXCPTION on error protocolResult process(size_t & bytesTransferred, ///< number of baytes in buffer char const * buffer ///< pointer to buffer holding transferred data ) { if (m_state == HTTP_DONE) { return PROTOCOL_COMPLETE; } if (m_state == HTTP_READING_CONTENT) { return PROTOCOL_HAVE_REQUEST; } for (size_t i = 0; i < bytesTransferred; ++i) { switch (m_state) { case HTTP_READING_METHOD: readMethod(buffer[i]); break; case HTTP_READING_URI: readUri(buffer[i]); break; case HTTP_READING_URI_PARAMETER_TAG: readUriParameterTag(buffer[i]); break; case HTTP_READING_URI_PARAMETER_VALUE: readUriParameterValue(buffer[i]); break; case HTTP_READING_VERSION: readVersion(buffer[i]); break; case HTTP_READING_RESPONSE_VERSION: readResponseVersion(buffer[i]); break; case HTTP_READING_RESPONSE_CODE: readResponseCode(buffer[i]); break; case HTTP_READING_RESPONSE_REASON: readResponseReason(buffer[i]); break; case HTTP_READING_HEADER_TAG: readHeaderTag(buffer[i]); break; case HTTP_READING_HEADER_VALUE: readHeaderValue(buffer[i]); break; case HTTP_READING_HEADER_VALUE_CONTINUATION: readHeaderValueContinuation(buffer[i]); break; case HTTP_READING_CONTENT: bytesTransferred -= i; return PROTOCOL_HAVE_REQUEST; break; case HTTP_DONE: bytesTransferred -= i; return PROTOCOL_COMPLETE; break; default: { throw ERROR_EXCEPTION << "internal protocol error missing case for protocol state: " << stateToString(); } } } // processed all the bytes in the buffer bytesTransferred = 0; if (m_state == HTTP_DONE) { return PROTOCOL_COMPLETE; } if (m_state == HTTP_READING_CONTENT) { return PROTOCOL_HAVE_REQUEST; } return PROTOCOL_NEED_MORE_DATA; } /// \brief get the request std::string request() { return m_uri; } /// \brief get the request parameters tagValue_t & parameters() { return m_uriParameters; } httpHeaders_t const & headers() { return m_headers; } /// \brief get the request data size /// /// this is value sent with Content-Length header std::size_t dataSize() { if (haveContent()) { return m_contentLength; } return 0; } /// \brief get the response code /// /// \see responsecode.h int responseCode() { return m_httpResponseCode.protocolToInternal(m_responseCode).m_code; } /// \brief get the response reason /// /// \see responsecode.h std::string responseReason() { return m_responseReason; } /// \brief formats login request, places formatted request in request void formatLoginRequest(std::string const& httpRequest, ///< http login request std::string const& cnonce, ///< client generated cnonce std::string const& hostId, ///< requester host id std::string const& digest, ///< digest to verify std::string& request, ///< request with params std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += httpRequest; request += '?'; request += HTTP_PARAM_TAG_CLIENT_NONCE; request += '='; request += urlEncode(cnonce); request += '&'; request += HTTP_PARAM_TAG_HOST; request += '='; request += urlEncode(hostId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats logout request, places formatted request in request void formatLogoutRequest(boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request with params std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_LOGOUT; request += '?'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } unsigned long long getEncodedHeadersLength(const httpHeaders_t & headers) { unsigned long long headerLength = 0; httpHeaders_t::const_iterator it = headers.begin(); for (; it != headers.end(); it++) { headerLength += AMPERSAND_LEN + (urlEncode(it->first)).size() + EQUAL_LEN + (urlEncode(it->second)).size(); } return headerLength; } /// \brief formats put file request, places formatted request in request void formatPutFileRequest(std::string const& putName, ///< put file name std::size_t dataSize, ///< size of data being sent in this request bool moreData, ///< indcates if more data to be sent in other requests (true: yes, false: no) boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress, ///< server ip address COMPRESS_MODE compressMode, ///< compress mode (see volumegroupsettings.h) const httpHeaders_t & headers, ///< additional headers to send in the request bool createDirs = false, ///< indicates if missing dirs should be created (true: yes, false: no) long long offset = PROTOCOL_DO_NOT_SEND_OFFSET ///< offset to read/write at ) { std::string offsetAsString; if (PROTOCOL_DO_NOT_SEND_OFFSET != offset) { offsetAsString = boost::lexical_cast<std::string>(offset); } std::string compressModeString(boost::lexical_cast<std::string>(compressMode)); // NOTE: need to url ecnode any data befor calculating total size below as // url encoding may increase the length. also make sure to use the // url encoded params when adding to the request std::string urlEncodedName(urlEncode(putName)); std::string id(urlEncode(digest)); std::string reqIdStr(boost::lexical_cast<std::string>(reqId)); request = HTTP_METHOD_POST; request += ' '; request += HTTP_REQUEST_PUTFILE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_HEADER_CONTENT_LENGTH; // putfile uses post so need to calculate the content length // add up the length of the tags and their values // note : // * do not forget to use url encoded sizes as they will be larger // * moredata and createdirs values are always 1 byte (either '0' or '1') // * offset is optional // * data= must be last request += boost::lexical_cast<std::string>(strlen(HTTP_PARAM_TAG_MORE_DATA) + EQUAL_LEN + MOREDATA_LEN + AMPERSAND_LEN + strlen(HTTP_PARAM_TAG_NAME) + EQUAL_LEN + urlEncodedName.size() + AMPERSAND_LEN + strlen(HTTP_PARAM_TAG_COMPRESS_MODE) + EQUAL_LEN + compressModeString.size() + AMPERSAND_LEN + strlen(HTTP_PARAM_TAG_CREATE_DIRS) + EQUAL_LEN + CREATEDIRS_LEN + AMPERSAND_LEN + (offsetAsString.empty() ? 0 : (strlen(HTTP_PARAM_TAG_OFFSET) + EQUAL_LEN + offsetAsString.size() + AMPERSAND_LEN)) + strlen(HTTP_PARAM_TAG_REQ_ID) + EQUAL_LEN + reqIdStr.size() + AMPERSAND_LEN + strlen(HTTP_PARAM_TAG_ID) + EQUAL_LEN + id.size() + AMPERSAND_LEN + HTTP_CURRENT_VER_TAG_VALUE.size() + getEncodedHeadersLength(headers) + strlen(HTTP_PARAM_TAG_DATA) + EQUAL_LEN + dataSize ); request += HTTP_EOL; request += HTTP_HEADER_CONNECTION_KEEP_ALIVE; request += HTTP_EOL; request += HTTP_EOL; // add post data request += HTTP_PARAM_TAG_MORE_DATA; request += '='; request += (moreData ? '1' : '0'); request += '&'; request += HTTP_PARAM_TAG_NAME; request += '='; request += urlEncodedName; request += '&'; request += HTTP_PARAM_TAG_COMPRESS_MODE; request += '='; request += compressModeString; request += '&'; request += HTTP_PARAM_TAG_CREATE_DIRS; request += '='; request += (createDirs ? '1' : '0'); request += '&'; if (!offsetAsString.empty()) { request += HTTP_PARAM_TAG_OFFSET; request += '='; request += offsetAsString; request += '&'; } request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += reqIdStr; request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += id; if (!headers.empty()) { httpHeaders_t::const_iterator it = headers.begin(); for (; it != headers.end(); it++) { request += '&' + urlEncode(it->first) + '=' + urlEncode(it->second); } } request += HTTP_CURRENT_VER_TAG_VALUE; request += '&'; request += HTTP_PARAM_TAG_DATA; // NOTE this must always be the last tag in putfile request request += '='; } /// \brief formats get file request, places formatted request in request void formatGetFileRequest(std::string const& name, ///< name of file to get boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_GETFILE; request += '?'; request += HTTP_PARAM_TAG_NAME; request += '='; request += urlEncode(name); request += '&'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats rename file request, places formatted request in request void formatRenameFileRequest(std::string const& oldName, ///< name of file to rename std::string const& newName, ///< name to rename to COMPRESS_MODE compressMode, ///< compress mode in affect (see compressmode.h) boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress, ///< server ip address const httpHeaders_t& headers, ///< additional headers to send in the request std::string const& finalPaths = std::string() ///< semi-colon (';') separated list of all paths that should get a "copy" of the renamed file ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_RENAMEFILE; request += '?'; request += HTTP_PARAM_TAG_OLDNAME; request += '='; request += urlEncode(oldName); request += '&'; request += HTTP_PARAM_TAG_NEWNAME; request += '='; request += urlEncode(newName); request += '&'; request += HTTP_PARAM_TAG_COMPRESS_MODE; request += '='; request += boost::lexical_cast<std::string>(compressMode); if (!finalPaths.empty()) { request += '&'; request += HTTP_PARAM_TAG_FINAL_PATHS; request += '='; request += urlEncode(finalPaths); } request += '&'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); httpHeaders_t::const_iterator it = headers.begin(); for (; it != headers.end(); it++) { request += '&' + urlEncode(it->first) + '=' + urlEncode(it->second); } request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats put delete request, places formatted request in request void formatDeleteFileRequest(std::string const& name, ///< semi-colon (';') separated list of names to be used during deletion processing std::string const& fileSpec, ///< semi-colon (';') separated list of file specs int mode, ///< mode to be used while deleting boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_DELETEFILE; request += '?'; request += HTTP_PARAM_TAG_NAME; request += '='; request += urlEncode(name); request += '&'; request += HTTP_PARAM_TAG_MODE; request += '='; request += boost::lexical_cast<std::string>(mode); if (!fileSpec.empty()) { request += '&'; request += HTTP_PARAM_TAG_FILESPEC; request += '='; request += urlEncode(fileSpec); } request += '&'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats list file request, places formatted request in request void formatListFileRequest(std::string const& fileSpec, ///< file specification to match against boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_LISTFILE; request += '?'; request += HTTP_PARAM_TAG_FILESPEC; request += '='; request += urlEncode(fileSpec); request += '&'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats cfs heartbeat request, places formatted request in request void formatHeartbeatRequest(boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string const& ipAddress, ///< server ip address char const* heartbeatReq = HTTP_REQUEST_HEARTBEAT /// specific type of heartbeat to send ) { request = HTTP_METHOD_GET; request += ' '; request += heartbeatReq; request += '?'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats cfs connect back sent to cfs to cxps to tell it to create a new connection back to cfs void formatCfsConnectBack(std::string const& cfsSessionId, ///< cfs session id requesting the connect back bool secure, ///< incidates if secure should be used true: yes, false: no boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_CFSCONNECTBACK; request += '?'; request += HTTP_PARAM_TAG_SESSIONID; request += '='; request += cfsSessionId; request += '&'; request += HTTP_PARAM_TAG_SECURE; request += '='; request += (secure ? "yes" : "no"); request += '&'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats cfs connect sent from cxps to cfs void formatCfsConnect(std::string const& cfsSessionId, bool secure, ///< incidates if secure should be used true: yes, false: no boost::uint32_t reqId, ///< requestId std::string const& digest, ///< digest to verify std::string& request, ///< request std::string ipAddress ///< server ip address ) { request = HTTP_METHOD_GET; request += ' '; request += HTTP_REQUEST_CFSCONNECT; request += '?'; request += HTTP_PARAM_TAG_SESSIONID; request += '='; request += cfsSessionId; request += '&'; request += HTTP_PARAM_TAG_SECURE; request += '='; request += (secure ? "yes" : "no"); request += '&'; request += HTTP_PARAM_TAG_REQ_ID; request += '='; request += boost::lexical_cast<std::string>(reqId); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += urlEncode(digest); request += HTTP_CURRENT_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats request response void formatResponse(ResponseCode::Codes result, ///< error code to return long long totalLength, ///< total length of data to be sent with response std::string& response, ///< stream to put formatted response into const std::map<std::string, std::string> & responseHeaders ///< headers to send in response ) { ResponseCode::Code code(m_httpResponseCode.internalToProtocol(result)); response = HTTP_VERSION; response += ' '; response += code.m_str; response += HTTP_EOL; if (totalLength > 0) { response += "Content-Type: text/plain\r\n"; // MAYBE: use a different content type for the actual file data response += "Content-Length: "; response += boost::lexical_cast<std::string>(totalLength); response += HTTP_EOL; } if (!responseHeaders.empty()) { for (std::map<std::string, std::string>::const_iterator headers = responseHeaders.begin(); headers != responseHeaders.end(); headers++) { response += headers->first + ":"; response += headers->second; response += HTTP_EOL; } } response += HTTP_EOL; } /// \brief formats request response void formatResponse(ResponseCode::Codes result, ///< error code to return long long totalLength, ///< total length of data to be sent with response std::string& response ///< stream to put formatted response into ) { std::map<std::string, std::string> emptyHeaders; formatResponse(result, totalLength, response, emptyHeaders); } /// \brief formats cs login request void formatCsLogin(std::string const& url, std::string const& nonce, std::string const& digest, std::string& request, std::string ipAddress) { request = HTTP_METHOD_GET; request += ' '; request += url; request += '?'; request += HTTP_PARAM_TAG_CS_ACTION; request += '='; request += CS_ACTION_CS_LOGIN; request += '&'; request += HTTP_PARAM_TAG_TAG; request += '='; request += nonce; request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += digest; request += '&'; request += HTTP_CURRENT_CS_API_VER_TAG_VALUE; // this is full tag=val request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_HEADER_USER_AGENT_CXPS; request += HTTP_EOL; request += HTTP_HEADER_ACCEPT; request += HTTP_MIME_JSON; request += HTTP_EOL; request += HTTP_HEADER_CONNECTION_CLOSE; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats get cfs connect info request void formatGetCfsConnectInfo(std::string const& id, std::string const& nonce, std::string const& url, std::string const& digest, std::string& request, std::string ipAddress) { request = HTTP_METHOD_GET; request += ' '; request += url; request += '?'; request += HTTP_PARAM_TAG_CS_ACTION; request += '='; request += CS_ACTION_GET_CONNECT_INFO; request += '&'; request += HTTP_PARAM_TAG_HOST; request += '='; request += id; request += '&'; request += HTTP_PARAM_TAG_TAG; request += '='; request += nonce; request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += digest; request += HTTP_CURRENT_CS_API_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_HEADER_USER_AGENT_CXPS; request += HTTP_EOL; request += HTTP_HEADER_ACCEPT; request += HTTP_MIME_JSON; request += HTTP_EOL; request += HTTP_HEADER_CONNECTION_CLOSE; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats get cfs heartbeat request void formatCfsHeartbeat(std::string const& id, std::string const& nonce, std::string const& url, std::string const& digest, std::string& request, std::string ipAddress) { request = HTTP_METHOD_GET; request += ' '; request += url; request += '?'; request += HTTP_PARAM_TAG_CS_ACTION; request += '='; request += CS_ACTION_CFS_HEARTBEAT; request += '&'; request += HTTP_PARAM_TAG_HOST; request += '='; request += id; request += '&'; request += HTTP_PARAM_TAG_TAG; request += '='; request += nonce; request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += digest; request += HTTP_CURRENT_CS_API_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_HEADER_USER_AGENT_CXPS; request += HTTP_EOL; request += HTTP_HEADER_ACCEPT; request += HTTP_MIME_JSON; request += HTTP_EOL; request += HTTP_HEADER_CONNECTION_CLOSE; request += HTTP_EOL; request += HTTP_EOL; } /// \brief formats get cfs heartbeat request void formatCfsError(std::string const& id, std::string const& nonce, std::string const& msg, std::string const& url, std::string const& digest, std::string& request, std::string ipAddress) { request = HTTP_METHOD_GET; request += ' '; request += url; request += '?'; request += HTTP_PARAM_TAG_CS_ACTION; request += '='; request += CS_ACTION_CFS_ERROR; request += '&'; request += HTTP_PARAM_TAG_HOST; request += '='; request += id; request += '&'; request += HTTP_PARAM_TAG_TAG; request += '='; request += nonce; request += '&'; request += HTTP_PARAM_TAG_MSG; request += '='; request += urlEncode(msg); request += '&'; request += HTTP_PARAM_TAG_ID; request += '='; request += digest; request += HTTP_CURRENT_CS_API_VER_TAG_VALUE; request += ' '; request += HTTP_VERSION; request += HTTP_EOL; request += HTTP_HEADER_HOST; request += ipAddress; request += HTTP_EOL; request += HTTP_HEADER_USER_AGENT_CXPS; request += HTTP_EOL; request += HTTP_HEADER_ACCEPT; request += HTTP_MIME_JSON; request += HTTP_EOL; request += HTTP_HEADER_CONNECTION_CLOSE; request += HTTP_EOL; request += HTTP_EOL; } protected: /// \brief get the current state as a string char const* stateToString() { switch (m_state) { case HTTP_ERROR: return "HTTP_ERROR"; case HTTP_READING_METHOD: return "HTTP_READING_METHOD"; case HTTP_READING_URI: return "HTTP_READING_URI"; case HTTP_READING_URI_PARAMETER_TAG: return "HTTP_READING_URI_PARAMETER_TAG"; case HTTP_READING_URI_PARAMETER_VALUE: return "HTTP_READING_URI_PARAMETER_VALUE"; case HTTP_READING_VERSION: return "HTTP_READING_VERSION"; case HTTP_READING_RESPONSE_VERSION: return "HTTP_READING_RESPONSE_VERSION"; case HTTP_READING_RESPONSE_CODE: return "HTTP_READING_RESPONSE_CODE"; case HTTP_READING_RESPONSE_REASON: return "HTTP_READING_RESPONSE_REASON"; case HTTP_READING_HEADER_TAG: return "HTTP_READING_HEADER_TAG"; case HTTP_READING_HEADER_VALUE: return "HTTP_READING_HEADER_VALUE"; case HTTP_READING_HEADER_VALUE_CONTINUATION: return "HTTP_READING_HEADER_VALUE_CONTINUATION"; case HTTP_READING_CONTENT: return "HTTP_READING_CONTENT"; case HTTP_DONE: return "HTTP_DONE"; default: return "Unknown state"; } } /// \brief indicates if there was any content data sent with the request /// /// \return /// \li \c true if there is content data /// \li \c false if no content data bool haveContent() { httpHeaders_t::iterator contentLength(m_headers.find("Content-Length")); if (m_headers.end() == contentLength) { return false; } boost::algorithm::trim((*contentLength).second); try { m_contentLength = boost::lexical_cast<std::size_t>((*contentLength).second); } catch (std::exception const& e) { throw ERROR_EXCEPTION << e.what() << " converting Content-Length: " << (*contentLength).second; } return (m_contentLength > 0); } /// \brief set next state after all headers have been processed void endOfHeadersState() { if (haveContent()) { m_state = HTTP_READING_CONTENT; } else { m_state = HTTP_DONE; } } /// \brief read method portion of the request /// /// \param token current token being parsed void readMethod(char token) { if (' ' == token) { if (m_method.empty()) { throw ERROR_EXCEPTION << "missing method"; } if (m_supportedMethods.end() == m_supportedMethods.find(m_method)) { throw ERROR_EXCEPTION << "unsupported method: " << m_method << " side: " << handlerSide(); } m_state = HTTP_READING_URI; } else if ('\r' == token || '\n' == token) { throw ERROR_EXCEPTION << "readMethod invalid format"; } else { m_method += token; } } /// \brief read uri portion up to but not including params (this is the request) /// /// \param token current token being parsed void readUri(char token) { if (' ' == token) { if (!m_uri.empty()) { m_state = HTTP_READING_VERSION; } } else if ('?' == token) { if (m_uri.empty()) { throw ERROR_EXCEPTION << "missing uri"; } else { m_uri = urlDecode(m_uri); m_state = HTTP_READING_URI_PARAMETER_TAG; } } else if ('\r' == token || '\n' == token) { throw ERROR_EXCEPTION << "readUri invalid format"; } else { m_uri += token; } } /// \brief read current uri tag portion of the request /// /// \param token current token being parsed void readUriParameterTag(char token) { if (' ' == token) { // have complete tag and possible value so save them in the request m_uriParameters.insert(std::make_pair(urlDecode(m_tag), urlDecode(m_value))); m_tag.clear(); m_value.clear(); m_state = HTTP_READING_VERSION; } else if ('=' == token) { if (m_tag.empty()) { throw ERROR_EXCEPTION << "missing uri tag"; } else { m_state = HTTP_READING_URI_PARAMETER_VALUE; } } else if ('&' == token) { // have complete tag and possible value so save them in the request m_uriParameters.insert(std::make_pair(urlDecode(m_tag), urlDecode(m_value))); m_tag.clear(); m_value.clear(); m_state = HTTP_READING_URI_PARAMETER_TAG; } else if ('\r' == token || '\n' == token) { // error throw ERROR_EXCEPTION << "readUriParameterTag invalid format"; } else { m_tag += token; } } /// \brief read current uri value protion of the request /// /// \param token current token being parsed void readUriParameterValue(char token) { if (' ' == token) { // have complete tag and possible value so save them in the request m_uriParameters.insert(std::make_pair(urlDecode(m_tag), urlDecode(m_value))); m_tag.clear(); m_value.clear(); m_state = HTTP_READING_VERSION; } else if ('&' == token) { // have complete tag and possible value so save them in the request m_uriParameters.insert(std::make_pair(urlDecode(m_tag), urlDecode(m_value))); m_tag.clear(); m_value.clear(); m_state = HTTP_READING_URI_PARAMETER_TAG; } else if ('\r' == token || '\n' == token) { throw ERROR_EXCEPTION << "readUriParameterValue invalid format"; } else { m_value += token; } } /// \brief read version portion of the request /// /// \param token current token being parsed void readVersion(char token) { if (m_haveCrExpectingLf && '\n' != token) { throw ERROR_EXCEPTION << "reade version invalid format"; } if ('\r' == token) { m_haveCrExpectingLf = true; } else if ('\n' == token) { m_haveCrExpectingLf = false; if (m_version.empty()) { throw ERROR_EXCEPTION << "missing version"; } m_state = HTTP_READING_HEADER_TAG; } else if (' ' != token) { m_version += token; } } /// \brief read the version portion of the request response /// /// \param token current token being parsed void readResponseVersion(char token) { if (' ' == token) { m_state = HTTP_READING_RESPONSE_CODE; } else if ('\n' == token || '\r' == token) { throw ERROR_EXCEPTION << "invalid response"; } else { m_version += token; } } /// \brief read the response code portion of the request response /// /// \param token current token being parsed void readResponseCode(char token) { if ('\n' == token || '\r' == token) { throw ERROR_EXCEPTION << "invalid response detected while reading response code"; } else if (' ' == token) { if (!m_responseCodeAsString.empty()) { try { m_responseCode = boost::lexical_cast<int>(m_responseCodeAsString); } catch (std::exception const& e) { throw ERROR_EXCEPTION << e.what() << " converting response code: " << m_responseCodeAsString; } m_state = HTTP_READING_RESPONSE_REASON; } } else { m_responseCodeAsString += token; } } /// \brief read the response reason portion of the request response /// /// \param token current token being parsed void readResponseReason(char token) { if (m_haveCrExpectingLf && '\n' != token) { throw ERROR_EXCEPTION << "invalid response format detected while reading response reason"; } if ('\r' == token) { m_haveCrExpectingLf = true; } else if ('\n' == token) { m_haveCrExpectingLf = false; // will allow resason to not be sent m_state = HTTP_READING_HEADER_TAG; } else if (' ' != token) { m_responseReason += token; } } /// \brief read the header tag /// /// \param token current token being parsed void readHeaderTag(char token) { if (m_haveCrExpectingLf && '\n' != token) { throw ERROR_EXCEPTION << "readHeaderTag invalid format"; } if (' ' == token) { // could be a header tag that has no value but some whitespace before // end of line, so only complain if we do not have any tag data if (m_tag.empty()) { throw ERROR_EXCEPTION << "missing header tag"; } } else if ('\r' == token) { m_haveCrExpectingLf = true; } else if ('\n' == token) { m_haveCrExpectingLf = false; if (m_tag.empty()) { // blank line - end of headers endOfHeadersState(); } else { // must be a header that has not values m_headers.insert(std::make_pair(m_tag, m_value)); m_tag.clear(); m_value.clear(); } } else if (':' == token) { // done with the tag m_state = HTTP_READING_HEADER_VALUE; } else { m_tag += token; } } /// \brief read the header value /// /// \param token current token being parsed void readHeaderValue(char token) { if (m_haveCrExpectingLf && '\n' != token) { throw ERROR_EXCEPTION << "readHeaderValue invalid format"; } if ('\r' == token) { m_haveCrExpectingLf = true; } else if ('\n' == token) { m_haveCrExpectingLf = false; if (m_tag.empty()) { // blank line - end of headers endOfHeadersState(); } else { m_state = HTTP_READING_HEADER_VALUE_CONTINUATION; } } else { m_value += token; } } /// \brief read a header value that continued on another line void readHeaderValueContinuation(char token) { if (m_haveCrExpectingLf && '\n' != token) { throw ERROR_EXCEPTION << "readHeaderValueContinuation invalid format"; } if ('\r' == token) { m_haveCrExpectingLf = true; } else if (' ' == token) { // is a value continuation add the space // and treat like a normal value now m_value += token; m_state = HTTP_READING_HEADER_VALUE; } else if ('\n' == token) { m_haveCrExpectingLf = false; // blank line - end of headers m_headers.insert(std::make_pair(m_tag, m_value)); m_tag.clear(); m_value.clear(); m_tag += token; endOfHeadersState(); } else { // have another tag m_headers.insert(std::make_pair(m_tag, m_value)); m_tag.clear(); m_value.clear(); m_tag += token; m_state = HTTP_READING_HEADER_TAG; } } private: protocolHandlerSide m_handlerSide; ///< indicates if used on server side or client side /// \brief internal states of the handler while it is processing the request. /// /// The names (hopefully) are obvious enough to indicate the current state with out need for more documentation enum state { HTTP_ERROR, HTTP_READING_METHOD, HTTP_READING_URI, HTTP_READING_URI_PARAMETER_TAG, HTTP_READING_URI_PARAMETER_VALUE, HTTP_READING_VERSION, HTTP_READING_RESPONSE_VERSION, HTTP_READING_RESPONSE_CODE, HTTP_READING_RESPONSE_REASON, HTTP_READING_HEADER_TAG, HTTP_READING_HEADER_VALUE, HTTP_READING_HEADER_VALUE_CONTINUATION, HTTP_READING_CONTENT, HTTP_DONE }; state m_state; ///< current state std::size_t m_contentLength; ///< content length (data size) bool m_haveCrExpectingLf; ///< true if read a CR and expect to see a LF std::string m_tag; ///< tracking the current uri/header tag being processed std::string m_value; ///< tracking the current uri/header value being processed // status line std::string m_responseReason; ///< response reason returned for a request std::string m_responseCodeAsString; ///< response code as string (as returned for a request) int m_responseCode; ///< response code (converted from the string) // request line std::string m_method; ///< request method std::string m_uri; ///< uri with out parameters (the request) tagValue_t m_uriParameters; ///< uri parameters after being parsed // common to response and requet std::string m_version; ///< version used (either request or response) httpHeaders_t m_headers; ///< parsed headers (either request or response) /// for converting http responses to internal responses /// /// \see responsecode.h HttpResponseCode m_httpResponseCode; std::set<std::string> m_supportedMethods; ///< holds the http methods that are supported }; #endif // PROTOCOLHANDLER_H