-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | include/kingate/cgi_gateway.h | 57 | ||||
-rw-r--r-- | kingate.pc.in | 4 | ||||
-rw-r--r-- | src/cgi_gateway.cc | 118 |
4 files changed, 183 insertions, 12 deletions
diff --git a/configure.ac b/configure.ac index b3141f1..b51d64d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,70 +1,84 @@ AC_INIT([kingate], [0.0.1], [kingate-bugs@klever.net]) AC_CONFIG_SRCDIR([include/kingate/cgi_gateway.h]) -AC_CONFIG_HEADER([config.h]) +AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([dist-bzip2]) AC_PROG_INSTALL AC_PROG_AWK AC_PROG_CXX AC_PROG_CC AC_PROG_LIBTOOL AC_HEADER_STDC AC_CHECK_HEADERS([sys/types.h sys/stat.h]) AC_CHECK_DECLS([environ],,,[ #include <unistd.h> ]) HAVE_FCGI=false AC_LANG_PUSH(C++) AC_CHECK_HEADERS([fcgio.h],[ AC_CHECK_LIB(fcgi,FCGX_Init,[ LIBS="-lfcgi ${LIBS}" AC_CHECK_LIB([fcgi++],[main],[ LIBS="-lfcgi++ ${LIBS}" HAVE_FCGI=true ]) ]) ]) AC_LANG_POP(C++) if ! ${HAVE_FCGI} ; then AC_MSG_NOTICE([no FastCGI development kit found. It is highly recommnded that you get one. Meanwhile we will proceed without FastCGI support]) fi AM_CONDITIONAL([HAVE_FCGI],[${HAVE_FCGI}]) AC_C_CONST AC_FUNC_MALLOC AC_FUNC_REALLOC AC_PATH_PROG([XSLTPROC],[xsltproc],[true]) AC_WITH_PKGCONFIG PKG_CHECK_MODULES([KONFORKA],[konforka],,[ AC_MSG_ERROR([no konforka library found. get one from http://kin.klever.net/konforka/]) ]) WANT_DOXYGEN="yes" AC_ARG_ENABLE([doxygen], AC_HELP_STRING([--disable-doxygen],[do not generate documentation]), [ test "${enableval}" = "no" && WANT_DOXYGEN="no" ] ) if test "${WANT_DOXYGEN}" = "yes" ; then AC_WITH_DOXYGEN AC_WITH_DOT else AM_CONDITIONAL([HAVE_DOXYGEN],[false]) AM_CONDITIONAL([HAVE_DOT],[false]) fi +HAVE_MIMETIC=false +AC_LANG_PUSH(C++) + AC_CHECK_LIB([mimetic],[main],[ + MIMETIC_LIBS=-lmimetic + HAVE_MIMETIC=true + ] + ) +AC_LANG_POP(C++) +AC_SUBST([MIMETIC_LIBS]) +AC_SUBST([MIMETIC_CFLAGS]) +if ${HAVE_MIMETIC} ; then + AC_DEFINE([HAVE_MIMETIC],,[defined in presence of mimetic]) +fi + AC_CONFIG_FILES([ Makefile kingate.pc kingate-fcgi.pc kingate-plaincgi.pc Doxyfile include/Makefile src/Makefile ]) AC_OUTPUT diff --git a/include/kingate/cgi_gateway.h b/include/kingate/cgi_gateway.h index f20d72b..a26b0ae 100644 --- a/include/kingate/cgi_gateway.h +++ b/include/kingate/cgi_gateway.h @@ -1,274 +1,325 @@ #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; /** + * Abstract base class for retrieving posted files. + */ + class basic_file_t { + public: + /** + * Retrieve file name. + * @return filename + */ + virtual const string& filename() const = 0; + /** + * Retrieve file content type. + * @return content type + */ + virtual const string& content_type() const = 0; + /** + * Retrieve file contents. + * @return reference to the stream for accessing file content. + */ + virtual istream& content() = 0; + virtual ~basic_file_t(); + }; + typedef basic_file_t *file_t; + /** + * The map holding information pertaining to files uploaded via post. + */ + typedef multimap<string,file_t> files_t; + /** + * Files uploaded via post + */ + files_t files; + /** * 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); + cgi_gateway(cgi_interface& ci,bool parsebody = true); + virtual ~cgi_gateway() throw(); + + /** + * Parse request body. + */ + void parse_request_body(); /** * 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. + * @return the parameter contents. * @see exception_notfound. */ const string& get_param(const string& n) const; + /** + * Check to see whether the file was uploaded in the request body. + * @param n the parameter name. + * @return true if yes. + */ + bool has_file(const string& n) const; + /** + * Retrieve the file uploaded in the request body. + * @param n the parameter name. + * @return the file. + * @see exception_notfound. + */ + const file_t get_file(const string& n) const; + file_t get_file(const string& n); /** * 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; + unsigned long 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. */ const string& server_name() const; /** * Retrieve the SERVER_PORT meta-variable (see RFC3875) * @return the port on which request was received. */ unsigned int server_port() const; /** * Retrieve the SERVER_PROTOCOL meta-variable (see RFC3875) * @return the protocol used for the request. */ const string& server_protocol() const; /** * Retrieve the SERVER_SOFTWARE meta-variable (see RFC3875) * @return the name and version of server software. */ const string& server_software() const; private: /** * Parse the query string, putting the parameters into the map * specified. * @param q the query string. * @param p destination parameters map. */ static void parse_query(string& q,params_t& p); }; } #endif /* __KINGATE_CGI_GATEWAY_H */ /* * vim:set ft=cpp: */ diff --git a/kingate.pc.in b/kingate.pc.in index 671faac..05cfe1d 100644 --- a/kingate.pc.in +++ b/kingate.pc.in @@ -1,11 +1,11 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: kingate Description: C++ CGI support library Version: @VERSION@ Requires: konforka -Libs: -L${libdir} -lkingate -Cflags: -I${includedir} +Libs: -L${libdir} -lkingate @MIMETIC_LIBS@ +Cflags: -I${includedir} @MIMETIC_CFLAGS@ diff --git a/src/cgi_gateway.cc b/src/cgi_gateway.cc index ab48f78..1706679 100644 --- a/src/cgi_gateway.cc +++ b/src/cgi_gateway.cc @@ -1,161 +1,267 @@ #include <errno.h> #include <ctype.h> +#include <sstream> #include "kingate/cgi_gateway.h" #include "kingate/util.h" #include "kingate/exception.h" +#include "config.h" +#ifdef HAVE_MIMETIC +# include <mimetic/mimeentity.h> +# include <mimetic/parser/itparser.h> +#endif /* HAVE_MIMETIC */ namespace kingate { + using mimetic::MimeEntity; + +#ifdef HAVE_MIMETIC + struct TornMimeEntity : public MimeEntity { + typedef istreambuf_iterator<char> it_type; + typedef it_type::iterator_category it_cat; + struct IParser : public mimetic::IteratorParser<it_type,it_cat> { + typedef mimetic::IteratorParser<it_type,it_cat> BT; + IParser(MimeEntity& me) + : BT::IteratorParser<it_type,it_cat>(me) { } + void loadHeader(it_type bit,it_type eit) { + m_bit = bit; m_eit = eit; + BT::loadHeader(); + } + void loadBody(it_type bit,it_type eit) { + m_bit = bit; m_eit = eit; + BT::loadBody(); + } + }; + void load(istream& hs,istream& bs,int mask=0) { + IParser prs(*this); + prs.iMask(mask); + prs.loadHeader(it_type(hs),it_type()); + prs.loadBody(it_type(bs),it_type()); + } + }; +#endif /* HAVE_MIMETIC */ static string empty_string; - cgi_gateway::cgi_gateway(cgi_interface& ci) + cgi_gateway::basic_file_t::~basic_file_t() { } + + class string_file_t : public cgi_gateway::basic_file_t { + public: + string _file_name; + string _content_type; + stringstream _content; + + string_file_t(const string& fn,const string& ct,const string& s) + : _file_name(fn), _content_type(ct), _content(s,ios::in) { } + const string& filename() const { return _file_name; } + const string& content_type() const { return _content_type; } + istream& content() { return _content; } + }; + + cgi_gateway::cgi_gateway(cgi_interface& ci,bool parsebody) : iface(ci), b_parsed_content(false) { // Fetch GET content try { string qs = get_meta("QUERY_STRING"); parse_query(qs,get); }catch(exception_notfound& enf) { } + if(parsebody) + parse_request_body(); + // Parse cookies + try { + cookies.parse_cookies(get_meta("HTTP_COOKIE")); + }catch(exception_notfound& enf) { } + } + + cgi_gateway::~cgi_gateway() throw() { + for(files_t::iterator i=files.begin();i!=files.end();++i) + delete i->second; + files.clear(); + } + + void cgi_gateway::parse_request_body() { + if(b_parsed_content) + throw konforka::exception(CODEPOINT,"request body is already parsed"); // Fetch POST content - if(!strcasecmp(content_type().c_str(),"application/x-www-form-urlencoded")) { + if(!strncasecmp( + content_type().c_str(), + "application/x-www-form-urlencoded", + sizeof("application/x-www-form-urlencoded")-1) ) { 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) { } +#ifdef HAVE_MIMETIC + else if(!strncasecmp( + content_type().c_str(), + "multipart/form-data", + sizeof("multipart/form-data")-1) ) { + stringstream h; + h + << "Content-Type: " << content_type() << "\r\n" + << "Content-Length: " << content_length() << "\r\n\n"; + TornMimeEntity me; + me.load(h,iface.in(),0); + mimetic::MimeEntityList& parts = me.body().parts(); + for(mimetic::MimeEntityList::iterator i=parts.begin();i!=parts.end();++i) { + MimeEntity *p = *i; + const mimetic::ContentDisposition& cd = p->header().contentDisposition(); + string n = cd.param("name"); + string fn = cd.param("filename"); + if(fn.empty()) { + post.insert(params_t::value_type(n,p->body())); + }else{ + const mimetic::ContentType& ct = p->header().contentType(); + files.insert(files_t::value_type(n,new string_file_t(fn,ct.str(),p->body()))); + } + } + b_parsed_content = true; + } +#endif /* HAVE_MIMETIC */ } 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"); } + bool cgi_gateway::has_file(const string& n) const { + return files.find(n) != files.end(); + } + const cgi_gateway::file_t cgi_gateway::get_file(const string& n) const { + files_t::const_iterator i = files.find(n); + if(i==files.end()) + throw exception_notfound(CODEPOINT,"no such parameter"); + return i->second; + } + cgi_gateway::file_t cgi_gateway::get_file(const string& n) { + files_t::const_iterator i = files.find(n); + if(i==files.end()) + throw exception_notfound(CODEPOINT,"no such parameter"); + return i->second; + } /* * 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) { |