/cgi/cgi.hpp

https://bitbucket.org/nileshgr/cxxcms/ · C++ Header · 563 lines · 181 code · 101 blank · 281 comment · 13 complexity · be509c8fe4df83d5bd994db026a25db1 MD5 · raw file

  1. #ifndef CGI_HPP
  2. #define CGI_HPP
  3. #include <global.hpp>
  4. #include <common/common.hpp>
  5. #include <ctime>
  6. #include <string>
  7. #include <memory>
  8. /*! \file cgi.hpp
  9. \brief %CGI namespace definition
  10. File contains definition of various classes and functions for the %CGI module
  11. */
  12. /*! \namespace CGI
  13. \brief The %CGI - %Common Gateway Interface module.
  14. The %Common Gateway Interface is the basic protocol by which a webserver communicates data to the application.
  15. The Query string, HTTP GET/POST, HTTP header, etc, data is available through the %CGI protocol (environment variables, stdin).
  16. */
  17. namespace CGI {
  18. //! Error codes for CGI namespace.
  19. enum {
  20. E_QS_NOT_SET, //!< Query string not set. \sa Parser::getQstr
  21. E_INVALID_HEX_SYMBOL, //!< Invalid hexadecimal symbol. \sa #decodeHex
  22. E_ENV_NOT_FOUND, //!< Environment variable not found. \sa Request::getEnv
  23. E_PARAM_NOT_FOUND, //!< Parameter not found. \sa Request::getParam Session::getParam Cookie::getParam
  24. E_INVALID_FILE_PTR, //!< Invalid file pointer. \sa Request::Request
  25. E_INVALID_CONTENT_LENGTH, //!< Invalid content length. \sa Request::Request
  26. E_COOKIE_REQUEST, //!< Cookie is in request mode. \sa Cookie::response
  27. E_SESSION_REQUEST, //!< Session is in request mode. \sa Session::response
  28. E_POST_BINARY, //!< POST data is binary. \sa Request::getData Request::getParam
  29. E_POST_NOT_BINARY, //!< POST data is not binary. \sa Request::getBinPost
  30. E_RESPONSE_BINARY, //!< Response is binary. \sa Response::getCompleteBody Response::getContentBody
  31. E_RESPONSE_NOT_BINARY, //!< Response is not binary. \sa Response::getBinaryData
  32. };
  33. /*! \brief Hex decoder
  34. Query string often contains characters in hex format which have special meaning.
  35. We need the actual representation/ASCII of the same.
  36. \param[in] source std::string containing a single hex code - \%XX
  37. \return int ASCII code of the character
  38. \coder{Nilesh G,nileshgr}
  39. */
  40. int decodeHex(std::string source);
  41. /*! \brief Hex encoder
  42. Hex encoder encodes a character which has a special meaning in the query string to
  43. it's hexadecimal representation (\%XX).
  44. \param[in,out] source std::string&
  45. \return std::string& source (processed)
  46. \coder{Ershad K,ershus}
  47. */
  48. std::string& encodeHex(std::string& source);
  49. /*! \brief Query string parser
  50. Query string is the main source of data for HTTP applications, because most of the requests are HTTP GET.
  51. The data is available in enviornment variable QUERY_STRING as per the %CGI/1.1 protocol.
  52. */
  53. class Parser {
  54. private:
  55. //! The raw query string supplied to CGI::Parser::Parser or CGI::Parser::setQstr
  56. std::string source;
  57. /*! \brief Query string sanitizer
  58. Removes certain string combinations in the query string that can cause hindrance while parsing it.
  59. \param[in,out] s The query string
  60. \param[in] n Offset to start checking from
  61. \coder{Ershad K,ershus}
  62. */
  63. void _sanitize(std::string& s, size_t n = 1) const;
  64. public:
  65. /*! \brief Constructor (1 parameter)
  66. \param[in] s Query string
  67. */
  68. Parser(std::string s) {
  69. source = s;
  70. }
  71. //! Default constructor
  72. Parser() = default;
  73. /*! \brief Sets the query string
  74. \param[in] s Query string
  75. \return const Parser& for cascading
  76. */
  77. const Parser& setQstr(std::string s) {
  78. source = s;
  79. return *this;
  80. }
  81. /*! \return Query string in const char* form
  82. \throw Common::Exception with #E_QS_NOT_SET if #source is empty
  83. */
  84. const char* getQstr() const {
  85. if(source.empty())
  86. throw Common::Exception("Query string propery requested while it was never set! in URL::Parser::getQstr()", E_QS_NOT_SET, __LINE__, __FILE__);
  87. return source.c_str();
  88. }
  89. /*! \brief Query string parser
  90. \return #Dict_ptr_t
  91. */
  92. Dict_ptr_t parse() const;
  93. };
  94. /*! \brief Class to manage sessions
  95. HTTP is a stateless protocol, hence we have to handle sessions on the server side.
  96. We track users by using a cookie for session id and storing relevant data on server side.
  97. \remark The session id, data, expire and response is common to all instances (static) and hence, over multiple instances, the methods
  98. will work on the same piece of %data instead of having their own copy.
  99. \note Module is not complete
  100. \todo Complete the module using database as storage.\n
  101. %Session destruction should be written to storage.
  102. \coder{Nilesh G,nileshgr}
  103. */
  104. class Session {
  105. private:
  106. static std::string id; //!< %Session identifier (id)
  107. static Dict_t data; //!< %Session data dictionary
  108. static time_t expire; //!< %Session expiry time
  109. static bool response; //!< Variable to track if it is in response mode or request mode
  110. public:
  111. /*! \brief Constructor to start/create a new session
  112. This class is inherited by Request, so we need to provide a default constructor.\n
  113. We will assume by default response mode here and if the other constructor Session(const std::string) gets called
  114. we will switch to request mode, hence prohibiting insertion/modification
  115. */
  116. Session();
  117. /*! \brief Constructor for loading existing session present in storage (request mode)
  118. \param[in] _id Session ID to be loaded
  119. \todo Throw Common::Exception if session is not found
  120. */
  121. Session(std::string _id);
  122. //! \return %Session ID
  123. const std::string getSessionId() const {
  124. return Session::id;
  125. }
  126. /*! \brief Method to retrive everything present in #data
  127. \remark Returns a new dictionary, modifications won't reflect.
  128. \sa getParam(const std::string)
  129. \return Dict_ptr_t of a newly allocated #Dict_t containing everything from #data
  130. */
  131. virtual Dict_ptr_t getData() const;
  132. /*! \brief Method to set parameter
  133. This is a virtual method because Response will have a wrapper.
  134. \throw Common::Exception with E_SESSION_REQUEST if class is in request mode
  135. \param[in] name Name of the session parameter
  136. \param[in] value Value of the session parameter
  137. \return const Session& for cascading operations
  138. */
  139. virtual const Session& setParam(std::string name, std::string value) {
  140. if(not response)
  141. throw Common::Exception("You are not allowed to set a session parameter in a request", E_SESSION_REQUEST, __LINE__, __FILE__);
  142. Session::data[name] = value;
  143. return *this;
  144. }
  145. /*! Method to retrieve a parameter
  146. This is a virtual method because Request has a wrapper.
  147. \param[in] name Name of the session parameter
  148. \return const std::string value from #data
  149. \throw Common::Exception with #E_PARAM_NOT_FOUND if key is not found in #data
  150. */
  151. virtual const std::string getParam(std::string name) const {
  152. Dict_t::iterator i;
  153. if((i = Session::data.find(name)) == Session::data.end())
  154. throw Common::Exception("Session parameter `" + name + "` was not found", E_PARAM_NOT_FOUND, __LINE__, __FILE__);
  155. return i->second;
  156. }
  157. /*! \brief Load session #data from an existing dictionary
  158. \remark Method might be removed after everything is glued properly and is not used.
  159. \param[in] _data Reference to #Dict_t from where data is to be loaded
  160. \return const Session& for cascading operations
  161. */
  162. const Session& loadData(const Dict_t& _data) {
  163. Session::data = _data;
  164. return *this;
  165. }
  166. /*! \brief Set the expire time
  167. \remark time_t is defined in time.h
  168. \param[in] _expire Seconds since UNIX Epoch
  169. \return Session& for cascading operations
  170. */
  171. Session& setExpireTime(time_t _expire) {
  172. Session::expire = _expire;
  173. return *this;
  174. }
  175. //! Retrieve expire time (since UNIX Epoch)
  176. time_t getExpireTime() const {
  177. return Session::expire;
  178. }
  179. };
  180. /*! \brief Structure to store cookies value and other attributes
  181. std::map will be used with key as name and value as this structure to store cookies
  182. */
  183. struct cookie_t {
  184. std::string value; //!< Value of cookie
  185. std::string path; //!< Path of cookie
  186. std::string domain; //!< Domain name (must start with a dot) of cookie
  187. time_t expire; //!< Expiration time since UNIX Epoch
  188. bool secure; //!< Cookie is HTTPS only?
  189. bool httponly; //!< Cookie only for HTTP requests
  190. // value, path and domain are initialized to empty string by string's constructor
  191. // We initialize expire, secure and httponly to 0, false, true by default
  192. cookie_t() : expire(0), secure(false), httponly(true) {}
  193. };
  194. /*! \brief Class to manage %Cookie data
  195. Cookies can be used for storage of data on the client side.\n
  196. This is basically a cookie-jar.
  197. \coder{Nilesh G,nileshgr}
  198. */
  199. class Cookie {
  200. public:
  201. typedef std::map<std::string, cookie_t> cookie_dict_t; //!< Type definition for cookie dictionary
  202. typedef std::pair<std::string, cookie_t> cookie_tuple_t; //!< Type definition for cookie pair
  203. private:
  204. cookie_dict_t cookies; //!< Dictionary to store cookies, consisting of name & cookie_t
  205. /*! \brief Type of jar - will be true if jar is for response
  206. If jar is in request mode, then only value will be used in cookie_t.
  207. */
  208. bool response;
  209. public:
  210. /*! \brief Constructor - Cookie Jar for response
  211. By default we assume response mode. If Cookie::Cookie(std::string) is called, we switch to request mode.
  212. */
  213. Cookie() : response (true) {}
  214. /*! \brief Constructor- cookie parser
  215. The constructor parses the string into #cookies
  216. \param[in] _cookies string containing cookies.\n
  217. Format: NAME1=VALUE1; NAME2=VALUE2; (see RFC 3875)
  218. \remark This sets #response to false \sa Cookie(bool)
  219. */
  220. Cookie(std::string _cookies);
  221. /*! \brief Returns a copy of cookie_t
  222. \param[in] name Name of the cookie
  223. \throw Common::Exception with #E_PARAM_NOT_FOUND if name is not found in #cookies
  224. \return cookie_t copy present in #cookies
  225. */
  226. cookie_t getCookie(std::string name) {
  227. cookie_dict_t::iterator i;
  228. if((i = cookies.find(name)) == cookies.end())
  229. throw Common::Exception("Cookie named `" + name + "` was not found", E_PARAM_NOT_FOUND, __LINE__, __FILE__);
  230. return i->second;
  231. }
  232. /*! \brief Sets a cookie
  233. \param[in] name string containing name of cookie
  234. \param[in] data cookie_t containing value and other parameters
  235. \return Cookie& for cascading operations
  236. \throw Common::Exception with E_COOKIE_REQUEST if response is false
  237. */
  238. Cookie& setCookie(std::string name, cookie_t data) {
  239. cookie_dict_t::iterator i;
  240. if(not response)
  241. throw Common::Exception("You are not allowed to set a cookie in request mode", E_COOKIE_REQUEST, __LINE__, __FILE__);
  242. cookies[name] = data;
  243. }
  244. /*! \brief Returns all cookies
  245. \return Copy of #cookies
  246. */
  247. cookie_dict_t getCookies() {
  248. return cookies;
  249. }
  250. };
  251. /*! \brief Class to manage HTTP %Request data
  252. When a client requests a resource, the webserver feeds the parameters via HTTP headers which are translated to environment variables
  253. (and stdin if POST) to the application. The methods in this class can be used to read data present in those.
  254. \coder{Nilesh G,nileshgr}
  255. */
  256. class Request : public Cookie, public Session {
  257. private:
  258. Dict_t env; //!< Dictionary to hold environment variables
  259. Dict_t get; //!< Dictionary to hold HTTP GET data
  260. Dict_t post; //!< Dictionary to hold HTTP POST data
  261. bool rawpostdata; //!< Variable to check if the POST data received was ASCII or binary (file upload)
  262. char *postBuffer; //!< If rawpostdata is true, then we cannot use post to store data, we need to use buffer
  263. char *postBuffer_fncall; //!< Pointer to the memory created and returned by #getBinPost, so that it can be deleted[]d on class destruction
  264. public:
  265. /*! \brief Options for which dictionary should be used
  266. #getData and #getParam use this to decide which dictionary to use.
  267. #getParam uses bitwise operators to search for the request key, hence multiple dictionaries to search for can be specifed
  268. */
  269. enum option_t {
  270. GET, //!< Use only HTTP GET data present in #get
  271. POST, //!< Use only HTTP POST data present in #post
  272. SESSION, //!< Use only CGI::Session
  273. ENV, //!< Use only environment variables data present in #env
  274. };
  275. /*! \brief Constructor
  276. \param[in] env Array of C-style strings for environment variables
  277. \throw Common::Exception with #E_INVALID_CONTENT_LENGTH if request mode is #POST and CONTENT_LENGTH = 0
  278. */
  279. Request(char** env);
  280. /*! \brief Returns all data or combination of requested data
  281. All the requested data is contained in the class variables, #get, #post, #env and data available from CGI::Session \n
  282. This function will return the requested one (#Dict_ptr_t) or if multiple ones are specified (bitwise operators)
  283. then the returned #Dict_t will contain combination of those.
  284. \sa Cookie::getCookies
  285. \param[in] option The dictionary which should be returned. Defaults to all values bitwise-or'd \sa #option_t
  286. \throw Common::Exception with #E_POST_BINARY if option has POST and #rawpostdata is true.
  287. \return #Dict_ptr_t for a #Dict_t containing the requested data
  288. */
  289. Dict_ptr_t getData(unsigned option = GET | POST | SESSION | ENV);
  290. /*! \brief Returns value of single request parameter
  291. The default order for searching is GPSE - Get, Post, %Session and Environment variables
  292. \sa Cookie::getCookie
  293. \param[in] name Name of the request parameter
  294. \param[in] option Dictionaries to search for. Defaults to all five of them (GPSE). \sa #option_t
  295. \return Value of the request parameter
  296. \throw Common::Exception with #E_PARAM_NOT_FOUND if requested parameter is not found in the specified dictionaries.
  297. \throw Common::Exception with #E_POST_BINARY if option has #POST and #rawpostdata is true
  298. */
  299. std::string getParam(std::string name, unsigned option = GET | POST | SESSION | ENV);
  300. /*! \brief Returns post data if it is binary (file upload)
  301. We cannot use getParam or getData if HTTP POST data is binary
  302. \remark Do not delete[] the pointer returned. It is take care of by ~Request
  303. \throw Common::Exception with #E_POST_NOT_BINARY if #rawpostdata = false
  304. \return char* #postBuffer_fncall
  305. */
  306. char* getBinPost();
  307. //! Destructor, to deallocate memory in #postBuffer and #postBuffer_fncall (if present)
  308. ~Request();
  309. };
  310. /*! \brief Class to manage response data
  311. Response body will be generated by this class depending on the parameters fed via methods.
  312. In this class, Cookie & Session will be used in write-mode.
  313. \coder{Nilesh G,nileshgr}
  314. */
  315. class Response : public Cookie, public Session {
  316. private:
  317. Dict_t headers; //!< Headers are sent before body and even Cookie is present in HTTP header.
  318. std::string completeBody; //!< The complete response body (includes headers)
  319. std::string contentBody; //!< Content body (response body excluding headers)
  320. /*! \brief Binary mode
  321. Binary mode should be used when we need to emit binary files, for instance generate image and emit it
  322. #binary is used to know if we should use binary mode or not
  323. \sa #binaryData
  324. */
  325. bool binary;
  326. std::unique_ptr<char[]> binaryData; //!< Holds pointer to binary data
  327. size_t binaryLength; //!< Holds length of binary data
  328. std::string headerString; //!< String to store headers (parsed)
  329. /*! \brief Parses #headers into #headerString
  330. Headers are sent as Name-value pairs separated by colon and CRLF.
  331. We translate #headers into this format and store it in #headerString, so that
  332. it can be directly prepended to the output
  333. */
  334. void setupHeaders();
  335. public:
  336. //! \brief Options for where data should be added when #setParam is called
  337. enum option_t {
  338. SESSION, //!< Set a session parameter
  339. HEADER, //!< Set a response header
  340. };
  341. /*! \brief Constructor, sets up intial values for various parameters
  342. The constructor sets the session cookie, and sets the default headers to the following values:\n
  343. Content-Encoding: utf-8
  344. Content-Type: text/html
  345. */
  346. Response();
  347. /*! \brief Sets parameters in the specified context
  348. If #HEADER is used as option then the parameter will be added to #headers
  349. \param name Name of parameter
  350. \param value Value of parameter
  351. \param option Context of parameter (#option_t)
  352. \return Response& for cascading operation
  353. */
  354. Response& setParam(std::string name, std::string value, option_t option) {
  355. if(option == HEADER)
  356. headers[name] = value;
  357. if(option == SESSION)
  358. Session::setParam(name, value);
  359. }
  360. /*! \brief Returns the specified parameter from the context
  361. If #HEADER is used as option then the paramter will be obtained from #headers
  362. \param name Name of parameter
  363. \param option Context of parameter (#option_t)
  364. \throw Common::Exception with #E_PARAM_NOT_FOUND if parameter is not found in the context
  365. \return std::string Value of parameter
  366. */
  367. std::string getParam(std::string name, option_t option);
  368. /*! \brief Appends data to #contentBody
  369. \param data Data to be appended
  370. \return Response& for cascading operations
  371. */
  372. Response& appendBody(std::string data) {
  373. if(binary)
  374. throw Common::Exception("Response is binary", E_RESPONSE_BINARY, __LINE__, __FILE__);
  375. contentBody += data;
  376. }
  377. /*! \brief Clears #contentBody, #completeBody and if #binary is true, #binaryData (deallocates memory)
  378. \return Response& for cascading operations
  379. */
  380. Response& clearBody();
  381. /*! \brief Returns content body
  382. \remark Reference is returned because body data might be large
  383. \throw Common::Exception with #E_RESPONSE_BINARY if #binary is true
  384. \return std::string& #contentBody
  385. */
  386. std::string& getContentBody() {
  387. if(binary)
  388. throw Common::Exception("Response is binary", E_RESPONSE_BINARY, __LINE__, __FILE__);
  389. return contentBody;
  390. }
  391. /*! \brief Returns complete body
  392. The processing happens here- conversion of cookies into HTTP headers and other headers
  393. present in #headers into the form in which them must be emitted
  394. \remark Reference is returned because the data might be large
  395. \throw Common::Exception with #E_RESPONSE_BINARY if #binary is true
  396. \return std::string& #completeBody
  397. */
  398. std::string& getCompleteBody();
  399. /*! \brief Add binary body
  400. \remark
  401. -# If this is used, content added via #appendBody will be discarded
  402. -# Sets #binary to true
  403. \param _binaryData std::unique_ptr holding pointer to block of data
  404. \param _binaryLength Length/size of data (bytes)
  405. \return Response& for cascading operations
  406. */
  407. Response& setBinaryBody(std::unique_ptr<char[]> _binaryData, size_t _binaryLength) {
  408. binary = true;
  409. binaryData = std::move(_binaryData);
  410. binaryLength = _binaryLength;
  411. }
  412. /*! \brief Returns binary body
  413. \throw Common::Exception with #E_RESPONSE_NOT_BINARY if #binary is false
  414. \return std::unique_ptr<char[]> of memory location containing the parsed header string and binary data.
  415. */
  416. std::unique_ptr<char[]> getBinaryBody();
  417. };
  418. }
  419. #endif