-rw-r--r-- | include/Makefile.am | 4 | ||||
-rw-r--r-- | include/kingate/cgi_gateway.h | 5 | ||||
-rw-r--r-- | include/kingate/cookies.h | 300 | ||||
-rw-r--r-- | include/kingate/headers.h | 94 | ||||
-rw-r--r-- | include/kingate/util.h | 13 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/cgi_gateway.cc | 4 | ||||
-rw-r--r-- | src/cookies.cc | 242 | ||||
-rw-r--r-- | src/headers.cc | 40 | ||||
-rw-r--r-- | src/util.cc | 53 |
10 files changed, 757 insertions, 2 deletions
diff --git a/include/Makefile.am b/include/Makefile.am index e0b778b..ee5cd51 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,7 +1,9 @@ nobase_include_HEADERS = \ kingate/cgi_gateway.h \ kingate/cgi_interface.h \ kingate/fastcgi.h \ kingate/exception.h \ kingate/util.h \ - kingate/plaincgi.h + kingate/plaincgi.h \ + kingate/cookies.h \ + kingate/headers.h diff --git a/include/kingate/cgi_gateway.h b/include/kingate/cgi_gateway.h index a5c4056..f20d72b 100644 --- a/include/kingate/cgi_gateway.h +++ b/include/kingate/cgi_gateway.h @@ -1,236 +1,241 @@ #ifndef __KINGATE_CGI_GATEWAY_H #define __KINGATE_CGI_GATEWAY_H #include <map> #include "kingate/cgi_interface.h" +#include "kingate/cookies.h" #ifndef __deprecated #if ( __GNUC__ == 3 && __GNUC_MINOR__ > 0 ) || __GNUC__ > 3 #define __deprecated __attribute__((deprecated)) #else #define __deprecated #endif #endif /** * @file * @brief the cgi_gateway -- main interface to CGI. */ namespace kingate { using namespace std; /** * The main class interfacing with the CGI environment. */ class cgi_gateway { public: /** * The interface to CGI environment (e.g. fastcgi). */ cgi_interface& iface; /** * The type describing map holding parameters parsed from query string or input. */ typedef multimap<string,string> params_t; /** * The GET-passed parameters. */ params_t get; /** * The POST-passed parameters. */ params_t post; /** + * Cookies passed. + */ + cookies_t cookies; + /** * Was the stdin content parsed? */ bool b_parsed_content; /** * @param ci the interface to use. */ cgi_gateway(cgi_interface& ci); /** * Check whether there is an 'environment' meta-variable with specific name * passed to CGI. * @param n variable name. * @return true if yes. * @see cgi_interface::has_meta() * @see get_meta() */ bool has_meta(const string& n) const { return iface.has_meta(n); } /** * Retrieve the 'environment' meta-variable value. * @param n variable name. * @return variable contents. * @see exception_notfound * @see cgi_interface::get_meta() */ const string& get_meta(const string& n) const { return iface.get_meta(n); } /** * fetch reference to the 'stdin' stream. * @return the reference to the corresponding istream object. * @see cgi_interface::in() */ istream& in() { return iface.in(); } /** * fetch reference to the 'stdout' stream. * @return the reference to the corresponding ostream object. * @see cgi_interface::out() */ ostream& out() { return iface.out(); } /** * fetch reference to the 'stderr' stream. * @return the reference to the corresponding ostream object. * @see cgi_interface::err() */ ostream& err() { return iface.err(); } /** * cast to the ostream -- fetches the reference to the 'stdout' * stream. * @see out() */ operator ostream& (void) { return out(); } /** * Check to see whether the parameter was passed via GET. * @param n the parameter name. * @return true if yes. */ bool has_GET(const string& n) const; /** * Retrieve the parameter passed via GET. * @param n the parameter name. * @return the parameter contents. * @see exception_notfound */ const string& get_GET(const string& n) const; /** * Check to see whether the parameter was passed via POST. * @param n the parameter name. * @return true if yes. */ bool has_POST(const string& n) const; /** * Retrieve the POST-parameter. * @param n the parameter name. * @return the parameter contents. * @see exception_notfound */ const string& get_POST(const string& n) const; /** * Check to see whether the parameter was passed either via POST or * GET. * @param n the parameter name. * @return true if yes. */ bool has_param(const string& n) const; /** * Retrieve the parameter passed either via POST or GET * (GET-parameter takes precedence). * @param n the parameter name. * @return true if yes. * @see exception_notfound. */ const string& get_param(const string& n) const; /** * Retrieve the POST content-type (as passed via CONTENT_TYPE * environment variable). * @return the content type. */ const string& __deprecated get_content_type() const; /** * Retrieve the POST content length (as passed via the * CONTENT_LENGTH environment variable). * @return the content length. */ unsigned long __deprecated get_content_length() const; /** * Check to see whether the content from stdin stream was parsed. * @return true if yes. */ bool is_content_parsed() const { return b_parsed_content; } /** * Retrieve the HTTP header value from the HTTP_ meta-variable. * (see RFC3875) * @param hn header field name. * @return the HTTP header value. */ const string& http_request_header(const string& hn) const; /** * Retrieve the AUTH_TYPE meta-variable (see RFC3875) * @return authentication type. */ const string& auth_type() const; /** * Retrieve the CONTENT_LENGTH meta-variable (see RFC3875) * @return size of the request message body. */ unsigned long cgi_gateway::content_length() const; /** * Retrieve the CONTENT_TYPE meta-variable (see RFC3875) * @return media type of the request message body. */ const string& content_type() const; /** * Retrieve the GATEWAY_INTERFACE meta-variable (see RFC3875) * @return the gateway interface dialect. */ const string& gateway_interface() const; /** * Retrieve the PATH_INFO meta-variable (see RFC3875) * @return path to be interpreted by the script. */ const string& path_info() const; /** * Retrieve the PATH_TRANSLATED meta-variable (see RFC3875) * @return the translated path to the document. */ const string& path_translated() const; /** * Retrieve the QUERY_STRING meta-variable (see RFC3875) * @return the query string. */ const string& query_string() const; /** * Retrieve the REMOTE_ADDR meta-variable (see RFC3875) * @return the network address of the remote host. */ const string& remote_addr() const; /** * Retrieve the REMOTE_HOST meta-variable (see RFC3875) * @return the fully qualified domain name of the client if * available. REMOTE_ADDR otherwise. * @see remote_addr() */ const string& remote_host() const; /** * Retrieve the REMOTE_IDENT meta-variable (see RFC3875) * @return remote user identity (see RFC1413). */ const string& remote_ident() const; /** * Retrieve the REMOTE_USER meta-variable (see RFC3875) * @return the authenticated user name. */ const string& remote_user() const; /** * Retrieve the REQUEST_METHOD meta-variable (see RFC3875) * @return the http request method. */ const string& request_method() const; /** * Retrieve the SCRIPT_NAME meta-variable (see RFC3875) * @return the uri path identifying the script. */ const string& script_name() const; /** * Retrieve the SERVER_NAME meta-variable (see RFC3875) * @return the server name of the script. */ diff --git a/include/kingate/cookies.h b/include/kingate/cookies.h new file mode 100644 index 0000000..83ef0c6 --- a/dev/null +++ b/include/kingate/cookies.h @@ -0,0 +1,300 @@ +#ifndef __KINGATE_COOKIES_H +#define __KINGATE_COOKIES_H + +#include <string> +#include <map> +#include <ostream> + +/** + * @file + * @brief cookies-related classes. + */ + +namespace kingate { + using namespace std; + + /** + * Class, holding the cookie with parameters. + */ + class cookie : public map<string,string> { + public: + /** + * Cookie name. + */ + string name; + /** + * Cookie value. + */ + string value; + + cookie() { } + /** + * @param n cookie name. + * @param v cookie value. + */ + cookie(const string& n,const string& v) + : name(n), value(v) { } + + /** + * set cookie parameter. + * @param p parameter name. + * @param v parameter value. + * @see _get_string() + */ + void _set_string(const string& p,const string& v); + + /** + * @param n cookie name. + * @see get_name() + */ + void set_name(const string& n) { name = n; } + /** + * @param v cookie value. + * @see set_value() + */ + void set_value(const string& v) { value = v; } + /** + * @param c coomment. + * @see get_comment() + * @see has_comment() + * @see unset_comment() + */ + void set_comment(const string& c); + /** + * @param d domain. + * @see get_domain() + * @see has_domain() + * @see unset_domain() + */ + void set_domain(const string& d); + /** + * @param ma max-age. + * @see get_max_age() + * @see has_max_age() + * @see unset_max_age() + */ + void set_max_age(const string& ma); + /** + * @param p path. + * @see get_path() + * @see has_path() + * @see unset_path() + */ + void set_path(const string& p); + /** + * set cookie security. + * @param s true if secure. + * @see get_secure() + * @see is_secure() + */ + void set_secure(bool s); + + /** + * @param e expiration time. + * @see get_expires() + * @see has_expires() + * @see unset_expires() + */ + void set_expires(const string& e); + + /** + * get cookie parameter. + * @param p parameter name. + * @return parameter value. + * @see _set_string() + */ + const string& _get_string(const string& p) const; + + /** + * @return cookie name. + * @see set_name() + */ + const string& get_name() const { return name; } + /** + * @return cookie value. + * @see set_value() + */ + const string& get_value() const { return value; } + /** + * @return cookie comment. + * @see set_comment() + * @see has_comment() + * @see unset_comment() + */ + const string& get_comment() const; + /** + * @return cookie domain. + * @see set_domain() + * @see has_domain() + * @see unset_domain() + */ + const string& get_domain() const; + /** + * @return cookie max-age. + * @see set_max_age() + * @see has_max_age() + * @see unset_max_age() + */ + const string& get_max_age() const; + /** + * @return cookie path. + * @see set_path() + * @see has_path() + * @see unset_path() + */ + const string& get_path() const; + /** + * @return cookie security. + * @see is_secure() + * @see set_secure() + */ + bool get_secure() const; + /** + * @return cookie security. + * @see get_secure() + * @see set_secure() + */ + bool is_secure() const { return get_secure(); } + + /** + * @return cookie expiration time. + * @see set_expires() + * @see has_expires() + * @see unset_expires() + */ + const string& get_expires() const; + + /** + * @return true if cookie has comment. + * @see set_comment() + * @see get_comment() + * @see unset_comment() + */ + bool has_comment() const; + /** + * @return true if cookie has domain. + * @see set_domain() + * @see get_domain() + * @see unset_domain() + */ + bool has_domain() const; + /** + * @return true if cookie has max-age. + * @see set_max_age() + * @see get_max_age() + * @see unset_max_age() + */ + bool has_max_age() const; + /** + * @return true if cookie has path. + * @see set_path() + * @see get_path() + * @see unset_path() + */ + bool has_path() const; + + /** + * @return true if cookie has expiration time. + * @see set_expires() + * @see get_expires() + * @see unset_expires() + */ + bool has_expires() const; + + /** + * rid cookie of comment. + * @see set_comment() + * @see get_comment() + * @see has_comment() + */ + void unset_comment(); + /** + * rid cookie of domain. + * @see set_domain() + * @see get_domain() + * @see has_domain() + */ + void unset_domain(); + /** + * rid cookie of max-age. + * @see set_max_age() + * @see get_max_age() + * @see has_max_age() + */ + void unset_max_age(); + /** + * rid cookie of path. + * @see set_path() + * @see get_path() + * @see has_path() + */ + void unset_path(); + + /** + * rid cookie of expiration time. + * @see set_expires() + * @see get_expires() + * @see has_expires() + */ + void unset_expires(); + + /** + * render the 'Set-Cookie' HTTP header according to RFC2109. + * Absolutely useless, only works with lynx. + * @return the rendered header content. + */ + string set_cookie_header_rfc2109() const; + /** + * render the 'Set-Cookie' header according to the early vague + * netscape specs and common practice. + * @return the rendered header content. + */ + string set_cookie_header() const; + }; + + /** + * Cookies container class. + */ + class cookies_t : public map<string,cookie> { + public: + + cookies_t() { } + /** + * @param s 'Cookie:' HTTP header contents to parse. + */ + cookies_t(const string& s) { parse_cookies(s); } + + /** + * @param c cookie to set. + */ + void set_cookie(const cookie& c) { (*this)[c.get_name()]=c; } + /** + * @param n cookie name to remove. + */ + void unset_cookie(const key_type& n) { erase(n); } + /** + * @param n cookie name. + * @return true if exists. + */ + bool has_cookie(const key_type& n) const; + /** + * Return the named cookie if one exists. + * @param n cookie name. + * @return const reference to cookie object. + */ + const cookie& get_cookie(const key_type& n) const; + /** + * Return the named cookie if one exists. + * @param n cookie name. + * @return reference to cookie object. + */ + cookie& get_cookie(const key_type& n); + + /** + * @param s HTTP 'Cookie' header content. + */ + void parse_cookies(const string& s); + }; +} + +#endif /* __KINGATE_COOKIES_H */ diff --git a/include/kingate/headers.h b/include/kingate/headers.h new file mode 100644 index 0000000..fb37fec --- a/dev/null +++ b/include/kingate/headers.h @@ -0,0 +1,94 @@ +#ifndef __KINGATE_HEADERS_H +#define __KINGATE_HEADERS_H + +#include <string> +#include <map> +#include <ostream> + +/** + * @file + * @brief the headers -- HTTP headers container class. + */ + +namespace kingate { + using namespace std; + + /** + * The container class for HTTP headers. + */ + class headers : public multimap<string,string> { + public: + + /** + * Reference header if one exists. + * @param k the header. + * @return reference to the content. + */ + const mapped_type& operator[](const key_type& k) const { + return get_header(k); + } + /** + * Reference header, creating one if needed. + * @param k the header. + * @return reference to the content. + */ + mapped_type& operator[](const key_type& k) { + return get_header(k); + } + + /** + * Set HTTP header. Remove all existent occurences of headers with + * this name. + * @param h header name. + * @param c header content. + */ + void set_header(const key_type& h,const mapped_type& c); + /** + * Add HTTP header. + * @param h header name. + * @param c header content. + */ + void add_header(const key_type& h,const mapped_type& c); + /** + * Remove named header. + * @param h header name. + */ + void unset_header(const key_type& h); + /** + * Return const reference to the existing header. + * @param h header name. + * @return reference to header content. + */ + const mapped_type& get_header(const key_type& h) const; + /** + * Return reference to the header, creating one if needed. + * @param h header name. + * @return reference to the content. + */ + mapped_type& get_header(const key_type& h); + /** + * Return the range of headers with a certain name. + * @param h header name. + * @return pair of const iterators with the beginning and the end + * of range. + */ + pair<const_iterator,const_iterator> get_headers(const key_type& h) const; + /** + * Return the range of headers with a certain name. + * @param h header name. + * @return pair of iterators with the beginning and the end + * of range. + */ + pair<iterator,iterator> get_headers(const key_type& h); + /** + * Inquire whether the named header exists. + * @param h header name. + * @return true if exists. + */ + bool has_header(const key_type& h) const; + + }; + +} + +#endif /* __KINGATE_HEADERS_H */ diff --git a/include/kingate/util.h b/include/kingate/util.h index 6024ccf..3fd96f6 100644 --- a/include/kingate/util.h +++ b/include/kingate/util.h @@ -1,49 +1,62 @@ #ifndef __KINGATE_UTIL_H #define __KINGATE_UTIL_H #include <string> #ifndef __deprecated #if ( __GNUC__ == 3 && __GNUC_MINOR__ > 0 ) || __GNUC__ > 3 #define __deprecated __attribute__((deprecated)) #else #define __deprecated #endif #endif namespace kingate { using namespace std; /** * Encode string for passing via URL. * @param str string unencoded. * @return the encoded string. */ string url_encode(const string& str); /** * Remove URL-encoding from the string. * @param str the URL-encoded string. * @return the decoded string. */ string url_decode(const string& str); /** + * Quote string for use in HTTP header. + * @param str the string to quote. + * @return the quoted string. + */ + string http_quoted_string(const string& str); + /** + * Quote string for use in HTTP header if necessary. + * @param str the string to quote. + * @return the quoted string or token left as is. + */ + string http_quote(const string& str); + + /** * deprecated alias to url_encode. * @see url_encode */ inline string __deprecated url_escape(const string& str) { return url_encode(str); } /** * deprecated alias to url_decode. * @see url_decode */ inline string __deprecated url_unescape(const string& str) { return url_decode(str); } } #endif /* __KINGATE_UTIL_H */ /* * vim:set ft=cpp: */ diff --git a/src/Makefile.am b/src/Makefile.am index 12bb1f8..2d462c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,25 +1,27 @@ lib_LTLIBRARIES = libkingate.la libkingate-plaincgi.la if HAVE_FCGI lib_LTLIBRARIES += libkingate-fcgi.la endif INCLUDES = -I${top_srcdir}/include -I${top_srcdir} AM_CXXFLAGS = ${KONFORKA_CFLAGS} LDADD = ${KONFORKA_LIBS} libkingate_la_SOURCES = \ cgi_gateway.cc \ cgi_interface.cc \ - util.cc + util.cc \ + cookies.cc \ + headers.cc libkingate_la_LDFLAGS = -version-info 2:0:0 libkingate_fcgi_la_SOURCES = \ fastcgi.cc libkingate_fcgi_la_LDFLAGS = -version-info 1:0:0 libkingate_plaincgi_la_SOURCES = \ plaincgi.cc libkingate_plaincgi_la_LDFLAGS = -version-info 1:0:0 EXTRA_DIST = ${libkingate_fcgi_la_SOURCES} diff --git a/src/cgi_gateway.cc b/src/cgi_gateway.cc index 30410f2..ab48f78 100644 --- a/src/cgi_gateway.cc +++ b/src/cgi_gateway.cc @@ -1,221 +1,225 @@ #include <errno.h> #include <ctype.h> #include "kingate/cgi_gateway.h" #include "kingate/util.h" #include "kingate/exception.h" namespace kingate { static string empty_string; cgi_gateway::cgi_gateway(cgi_interface& ci) : iface(ci), b_parsed_content(false) { // Fetch GET content try { string qs = get_meta("QUERY_STRING"); parse_query(qs,get); }catch(exception_notfound& enf) { } // Fetch POST content if(!strcasecmp(content_type().c_str(),"application/x-www-form-urlencoded")) { unsigned long cl = content_length(); if(cl) { char * tmp = new char[cl]; iface.in().read(tmp,cl); string qs(tmp,cl); delete tmp; parse_query(qs,post); } b_parsed_content = true; } + // Parse cookies + try { + cookies.parse_cookies(get_meta("HTTP_COOKIE")); + }catch(exception_notfound& enf) { } } bool cgi_gateway::has_GET(const string& n) const { return get.find(n) != get.end(); } const string& cgi_gateway::get_GET(const string& n) const { params_t::const_iterator i = get.find(n); if(i==get.end()) throw exception_notfound(CODEPOINT,"no such parameter"); return i->second; } bool cgi_gateway::has_POST(const string& n) const { return post.find(n) != post.end(); } const string& cgi_gateway::get_POST(const string& n) const { params_t::const_iterator i = post.find(n); if(i==post.end()) throw exception_notfound(CODEPOINT,"no such parameter"); return i->second; } bool cgi_gateway::has_param(const string& n) const { return has_GET(n) || has_POST(n); } const string& cgi_gateway::get_param(const string& n) const { params_t::const_iterator i = get.find(n); if(i!=get.end()) return i->second; i = post.find(n); if(i!=post.end()) return i->second; throw exception_notfound(CODEPOINT,"no such parameter"); } /* * deprecated stuff. */ const string& cgi_gateway::get_content_type() const { if(!has_meta("CONTENT_TYPE")) return empty_string; return get_meta("CONTENT_TYPE"); } unsigned long cgi_gateway::get_content_length() const { if(!has_meta("CONTENT_LENGTH")) return 0; string cl = get_meta("CONTENT_LENGTH"); return strtol(cl.c_str(),NULL,10); } /* * */ const string& cgi_gateway::http_request_header(const string& hn) const { string mvn = "HTTP_"; for(const char* p=hn.c_str();*p;p++) { if(*p=='-') mvn += '_'; else mvn += toupper(*p); } return get_meta(mvn); } const string& cgi_gateway::auth_type() const { try { return get_meta("AUTH_TYPE"); }catch(exception_notfound& enf) { return empty_string; } } unsigned long cgi_gateway::content_length() const { try { const string& cl = get_meta("CONTENT_LENGTH"); errno = 0; const char *clp = cl.c_str(); unsigned long rv = strtol(clp,(char**)&clp,10); if(errno || *clp) throw server_error(CODEPOINT,"Invalid CONTENT_LENGTH value passed from server"); return rv; }catch(exception_notfound& enf) { return 0; } } const string& cgi_gateway::content_type() const { try { return get_meta("CONTENT_TYPE"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::gateway_interface() const { try { return get_meta("GATEWAY_INTERFACE"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::path_info() const { try { return get_meta("PATH_INFO"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::path_translated() const { try { return get_meta("PATH_TRANSLATED"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::query_string() const { try { return get_meta("QUERY_STRING"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::remote_addr() const { try { return get_meta("REMOTE_ADDR"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::remote_host() const { try { return get_meta("REMOTE_HOST"); }catch(exception_notfound& enf) { return remote_addr(); } } const string& cgi_gateway::remote_ident() const { try { return get_meta("REMOTE_IDENT"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::remote_user() const { try { return get_meta("REMOTE_USER"); }catch(exception_notfound& enf) { return empty_string; } } const string& cgi_gateway::request_method() const { try { return get_meta("REQUEST_METHOD"); }catch(exception_notfound& enf) { throw server_error(CODEPOINT,"No REQUEST_METHOD passed from server"); } } const string& cgi_gateway::script_name() const { try { return get_meta("SCRIPT_NAME"); }catch(exception_notfound& enf) { throw server_error(CODEPOINT,"No SCRIPT_NAME passed from server"); } } const string& cgi_gateway::server_name() const { try { return get_meta("SERVER_NAME"); }catch(exception_notfound& enf) { throw server_error(CODEPOINT,"No SERVER_NAME passed from server"); } } unsigned int cgi_gateway::server_port() const { try { const string& sp = get_meta("SERVER_PORT"); errno = 0; const char *spp = sp.c_str(); unsigned int rv = strtol(spp,(char**)&spp,10); if(errno || *spp) throw server_error(CODEPOINT,"Invalid SERVER_PORT value passed from server"); return rv; }catch(exception_notfound& enf) { throw server_error(CODEPOINT,"No SERVER_PORT passed from server"); } } const string& cgi_gateway::server_protocol() const { try { return get_meta("SERVER_PROTOCOL"); }catch(exception_notfound& enf) { throw server_error(CODEPOINT,"No SERVER_PROTOCOL passed from server"); } } const string& cgi_gateway::server_software() const { try { return get_meta("SERVER_SOFTWARE"); }catch(exception_notfound& enf) { throw server_error(CODEPOINT,"No SERVER_SOFTWARE passed from server"); } diff --git a/src/cookies.cc b/src/cookies.cc new file mode 100644 index 0000000..40a0c8b --- a/dev/null +++ b/src/cookies.cc @@ -0,0 +1,242 @@ +#include "kingate/cookies.h" +#include "kingate/util.h" +#include "kingate/exception.h" + +namespace kingate { + + /* + * RFC 2109: + * av-pairs = av-pair *(";" av-pair) + * av-pair = attr ["=" value] ; optional value + * attr = token + * value = word + * word = token | quoted-string + */ + + /* RFC 2109: + * + * The origin server effectively ends a session by sending the client a + * Set-Cookie header with Max-Age=0. + * + * An origin server may include multiple Set-Cookie headers in a response. + * Note that an intervening gateway could fold multiple such headers into a + * single header. + * + * + * set-cookie = "Set-Cookie:" cookies + * cookies = 1#cookie + * cookie = NAME "=" VALUE *(";" cookie-av) + * NAME = attr + * VALUE = value + * cookie-av = "Comment" "=" value + * | "Domain" "=" value + * | "Max-Age" "=" value + * | "Path" "=" value + * | "Secure" + * | "Version" "=" 1*DIGIT + * + * + * The origin server should send the following additional HTTP/1.1 + * response headers, depending on circumstances: + * + * * To suppress caching of the Set-Cookie header: Cache-control: no- + * cache="set-cookie". + * + * and one of the following: + * + * * To suppress caching of a private document in shared caches: Cache- + * control: private. + * + * * To allow caching of a document and require that it be validated + * before returning it to the client: Cache-control: must-revalidate. + * + * * To allow caching of a document, but to require that proxy caches + * (not user agent caches) validate it before returning it to the + * client: Cache-control: proxy-revalidate. + * + * * To allow caching of a document and request that it be validated + * before returning it to the client (by "pre-expiring" it): + * Cache-control: max-age=0. Not all caches will revalidate the + * document in every case. + * + * HTTP/1.1 servers must send Expires: old-date (where old-date is a + * date long in the past) on responses containing Set-Cookie response + * headers unless they know for certain (by out of band means) that + * there are no downsteam HTTP/1.0 proxies. HTTP/1.1 servers may send + * other Cache-Control directives that permit caching by HTTP/1.1 + * proxies in addition to the Expires: old-date directive; the Cache- + * Control directive will override the Expires: old-date for HTTP/1.1 + * proxies. + * + */ + + void cookie::_set_string(const string& p,const string& v) { + (*this)[p]=v; + } + + void cookie::set_comment(const string& c) { + _set_string("comment",c); + } + void cookie::set_domain(const string& d) { + _set_string("domain",d); + } + void cookie::set_max_age(const string& ma) { + _set_string("max-age",ma); + } + void cookie::set_path(const string& p) { + _set_string("path",p); + } + void cookie::set_secure(bool s) { + if(s) + _set_string("secure",""); + else + erase("secure"); + } + + void cookie::set_expires(const string& e) { + (*this)["expires"] = e; + } + + const string& cookie::_get_string(const string& s) const { + const_iterator i = find(s); + if(i==end()) + throw exception_notfound(CODEPOINT,"No parameter set"); + return i->second; + } + + const string& cookie::get_comment() const { + return _get_string("comment"); + } + const string& cookie::get_domain() const { + return _get_string("domain"); + } + const string& cookie::get_max_age() const { + return _get_string("max-age"); + } + const string& cookie::get_path() const { + return _get_string("path"); + } + bool cookie::get_secure() const { + return find("secure")!=end(); + } + + const string& cookie::get_expires() const { + return _get_string("expires"); + } + + bool cookie::has_comment() const { + return find("comment")!=end(); + } + bool cookie::has_domain() const { + return find("domain")!=end(); + } + bool cookie::has_max_age() const { + return find("max-age")!=end(); + } + bool cookie::has_path() const { + return find("path")!=end(); + } + + bool cookie::has_expires() const { + return find("expires")!=end(); + } + + void cookie::unset_comment() { + erase("comment"); + } + void cookie::unset_domain() { + erase("domain"); + } + void cookie::unset_max_age() { + erase("max-age"); + } + void cookie::unset_path() { + erase("path"); + } + + void cookie::unset_expires() { + erase("expires"); + } + + string cookie::set_cookie_header_rfc2109() const { + string rv = name + "=" + http_quoted_string(value); + for(const_iterator i=begin();i!=end();++i) { + if(i->first=="secure") { + rv += "; secure"; + }else{ + rv += "; "+i->first+"="+http_quote(i->second); + } + } + rv += "; Version=1"; + return rv; + } + + string cookie::set_cookie_header() const { + string rv = name + "=" + value; + for(const_iterator i=begin();i!=end();++i) { + if(i->first=="secure") { + rv += "; secure"; + }else{ + rv += "; "+i->first+"="+i->second; + } + } + return rv; + } + + bool cookies_t::has_cookie(const key_type& n) const { + return find(n)!=end(); + } + + const cookie& cookies_t::get_cookie(const key_type& n) const { + const_iterator i=find(n); + if(i==end()) + throw exception_notfound(CODEPOINT,"No cookie with such name found"); + return i->second; + } + + cookie& cookies_t::get_cookie(const key_type& n) { + iterator i=find(n); + if(i==end()) + throw exception_notfound(CODEPOINT,"No cookie with such name found"); + return i->second; + } + + void cookies_t::parse_cookies(const string& s) { + string str = s; + while(!str.empty()) { + string::size_type sc = str.find(';'); + string s; + if(sc==string::npos) { + s = str; + str.erase(); + }else{ + s = str.substr(0,sc); + str.erase(0,sc+1); + } + string::size_type nsp=s.find_first_not_of(" \t"); + if((nsp!=string::npos) && nsp) + s.erase(0,nsp); + string::size_type eq=s.find('='); + if(eq==string::npos) + continue; + string n = s.substr(0,eq); + s.erase(0,eq+1); + nsp = n.find_last_not_of(" \t"); + n.erase(nsp+1); + nsp = s.find_first_not_of(" \t"); + string v; + if(nsp!=string::npos) + v = s.substr(nsp); + else + v = s; + nsp = v.find_last_not_of(" \t"); + if(nsp==string::npos) + v.erase(); + else + v.erase(nsp+1); + cookie& c = (*this)[n]; + c.set_name(n); c.set_value(v); + } + } + +} diff --git a/src/headers.cc b/src/headers.cc new file mode 100644 index 0000000..89ac519 --- a/dev/null +++ b/src/headers.cc @@ -0,0 +1,40 @@ +#include "kingate/headers.h" +#include "kingate/exception.h" + +namespace kingate { + + void headers::set_header(const key_type& h,const mapped_type& c) { + erase(h); + insert(value_type(h,c)); + } + void headers::add_header(const key_type& h,const mapped_type& c) { + insert(value_type(h,c)); + } + void headers::unset_header(const key_type& h) { + erase(h); + } + const headers::mapped_type& headers::get_header(const key_type& h) const { + const_iterator i=find(h); + if(i==end()) + throw exception_notfound(CODEPOINT,"No such header"); + return i->second; + } + headers::mapped_type& headers::get_header(const key_type& h) { + // XXX: or should it fail if there's no such thing, unlike operator[]? + iterator i=find(h); + if(i==end()) + i = insert(value_type(h,"")); + return i->second; + } + + pair<headers::const_iterator,headers::const_iterator> headers::get_headers(const key_type& h) const { + return equal_range(h); + } + pair<headers::iterator,headers::iterator> headers::get_headers(const key_type& h) { + return equal_range(h); + } + + bool headers::has_header(const key_type& h) const { + return find(h)!=end(); + } +} diff --git a/src/util.cc b/src/util.cc index 3166e62..48e486a 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1,53 +1,106 @@ #include "kingate/util.h" #include "kingate/exception.h" namespace kingate { static const char *safeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "_-" ; string url_encode(const string& str) { string rv = str; string::size_type screwed = 0; for(;;) { screwed = rv.find_first_not_of(safeChars,screwed); if(screwed == string::npos) break; while(screwed<rv.length() && !strchr(safeChars,rv.at(screwed))) { char danger = rv.at(screwed); if(danger==' ') { rv.replace(screwed++,1,1,'+'); }else{ static char tmp[4] = {'%',0,0,0}; snprintf(&tmp[1],3,"%02X",0xFF&(int)danger); rv.replace(screwed,1,tmp,3); screwed+=3; } } } return rv; } string url_decode(const string& str) { string rv = str; string::size_type unscrewed = 0; for(;;) { unscrewed = rv.find_first_of("%+",unscrewed); if(unscrewed == string::npos) break; if(rv.at(unscrewed)=='+') { rv.replace(unscrewed++,1,1,' '); }else{ if((rv.length()-unscrewed)<3) throw exception(CODEPOINT,"incorrectly escaped string"); // XXX: ensure it's hex? int danger = strtol(rv.substr(unscrewed+1,2).c_str(),NULL,16); rv.replace(unscrewed,3,1,danger); unscrewed++; } } return rv; } + + /* + * RFC 2616: + * + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ + + /* + * RFC 2616: + * + * token = 1*<any CHAR except CTLs or separators> + */ + + static const char *quotible_chars = + "\001\002\003\004\005\006\007\010" + "\011\012\013\014\015\016\017\020" + "\021\022\023\024\025\026\027\030" + "\031\032\033\034\035\036\037\040" + "()<>@,;:\\\"/[]?={}" /* separator chars (except for SP and HT mentioned elsewhere */ + "\177" + ; + + /* + * RFC 2616: + * + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + * + * The backslash character ("\") MAY be used as a single-character + * quoting mechanism only within quoted-string and comment constructs. + * + * quoted-pair = "\" CHAR + */ + + string http_quoted_string(const string& str) { + string rv = str; + string::size_type sp=0; + for(string::size_type q=rv.find('"');(q=rv.find('"',q))!=string::npos;q+=2) + rv.insert(q,1,'\\'); + rv.insert(0,1,'"'); + rv += '"'; + return rv; + } + + string http_quote(const string& str) { + if(str.find_first_of(quotible_chars)==string::npos) + return str; + return http_quoted_string(str); + } + } |