PageRenderTime 45ms CodeModel.GetById 12ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

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