From f1d2eedebe6f47893801be42e8353695bb2f2e77 Mon Sep 17 00:00:00 2001 From: Christian Hofstaedtler Date: Mon, 9 Jun 2014 15:28:31 +0200 Subject: [PATCH] Sync embedded yahttp copy cmouse/yahttp@e8b4f1493e7c6a659102878c9b02fa7f1701464b Including automake integration from @cmouse. --- configure.ac | 2 + pdns/Makefile-recursor | 2 +- pdns/ext/yahttp/.gitignore | 2 + pdns/ext/yahttp/Makefile | 22 -- pdns/ext/yahttp/Makefile.am | 1 + pdns/ext/yahttp/yahttp/Makefile | 14 - pdns/ext/yahttp/yahttp/Makefile.am | 3 + pdns/ext/yahttp/yahttp/cookie.hpp | 134 ++++++++ pdns/ext/yahttp/yahttp/exception.hpp | 13 +- pdns/ext/yahttp/yahttp/reqresp.cpp | 408 ++++++++++--------------- pdns/ext/yahttp/yahttp/reqresp.hpp | 302 +++++++++++++----- pdns/ext/yahttp/yahttp/router.cpp | 163 ++++++++++ pdns/ext/yahttp/yahttp/router.hpp | 74 +++++ pdns/ext/yahttp/yahttp/url.hpp | 16 +- pdns/ext/yahttp/yahttp/utility.hpp | 193 +++++++++++- pdns/ext/yahttp/yahttp/yahttp-config.h | 2 + pdns/ext/yahttp/yahttp/yahttp.hpp | 33 ++ 17 files changed, 1004 insertions(+), 380 deletions(-) delete mode 100644 pdns/ext/yahttp/Makefile create mode 100644 pdns/ext/yahttp/Makefile.am delete mode 100644 pdns/ext/yahttp/yahttp/Makefile create mode 100644 pdns/ext/yahttp/yahttp/Makefile.am create mode 100644 pdns/ext/yahttp/yahttp/cookie.hpp create mode 100644 pdns/ext/yahttp/yahttp/router.cpp create mode 100644 pdns/ext/yahttp/yahttp/router.hpp create mode 100644 pdns/ext/yahttp/yahttp/yahttp-config.h diff --git a/configure.ac b/configure.ac index 84dec8152..220456990 100644 --- a/configure.ac +++ b/configure.ac @@ -320,6 +320,8 @@ AC_CONFIG_FILES([ pdns/Makefile codedocs/Makefile pdns/pdns + pdns/ext/yahttp/Makefile + pdns/ext/yahttp/yahttp/Makefile modules/bindbackend/Makefile modules/db2backend/Makefile modules/geobackend/Makefile diff --git a/pdns/Makefile-recursor b/pdns/Makefile-recursor index b06087d15..722788aec 100644 --- a/pdns/Makefile-recursor +++ b/pdns/Makefile-recursor @@ -23,7 +23,7 @@ rec_channel.o rec_channel_rec.o selectmplexer.o sillyrecords.o \ dns_random.o ext/polarssl-1.3.2/library/aes.o ext/polarssl-1.3.2/library/padlock.o dnslabeltext.o \ lua-pdns.o lua-recursor.o randomhelper.o recpacketcache.o dns.o \ reczones.o base32.o nsecrecords.o json.o ws-recursor.o ws-api.o \ -version.o responsestats.o webserver.o ext/yahttp/yahttp/reqresp.o \ +version.o responsestats.o webserver.o ext/yahttp/yahttp/reqresp.o ext/yahttp/yahttp/router.o \ rec-carbon.o REC_CONTROL_OBJECTS=rec_channel.o rec_control.o arguments.o misc.o \ diff --git a/pdns/ext/yahttp/.gitignore b/pdns/ext/yahttp/.gitignore index 69059d61c..3ef00e8b3 100644 --- a/pdns/ext/yahttp/.gitignore +++ b/pdns/ext/yahttp/.gitignore @@ -1,2 +1,4 @@ yahttp/libyahttp.a *.o +Makefile +Makefile.in diff --git a/pdns/ext/yahttp/Makefile b/pdns/ext/yahttp/Makefile deleted file mode 100644 index c9fe151da..000000000 --- a/pdns/ext/yahttp/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -.SILENT: - -all: - $(MAKE) -C yahttp all - -install: - -uninstall: - -distclean: clean - -clean: - $(MAKE) -C yahttp clean - -check: - -distdir: - mkdir -p $(distdir) - cp LICENSE Makefile $(distdir) - mkdir $(distdir)/yahttp - cp yahttp/Makefile yahttp/*.cpp yahttp/*.hpp $(distdir)/yahttp - diff --git a/pdns/ext/yahttp/Makefile.am b/pdns/ext/yahttp/Makefile.am new file mode 100644 index 000000000..f7da84ab9 --- /dev/null +++ b/pdns/ext/yahttp/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=yahttp diff --git a/pdns/ext/yahttp/yahttp/Makefile b/pdns/ext/yahttp/yahttp/Makefile deleted file mode 100644 index d95e498a1..000000000 --- a/pdns/ext/yahttp/yahttp/Makefile +++ /dev/null @@ -1,14 +0,0 @@ - -all: libyahttp.a - - -clean: - -rm -f *.o *.a - - -reqresp.o: *.cpp *.hpp - - -libyahttp.a: reqresp.o - echo AR $@ - $(AR) rcs $@ $< diff --git a/pdns/ext/yahttp/yahttp/Makefile.am b/pdns/ext/yahttp/yahttp/Makefile.am new file mode 100644 index 000000000..2595854f4 --- /dev/null +++ b/pdns/ext/yahttp/yahttp/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES=libyahttp.la + +libyahttp_la_SOURCES=cookie.hpp exception.hpp reqresp.cpp reqresp.hpp router.cpp router.hpp url.hpp utility.hpp yahttp-config.h yahttp.hpp diff --git a/pdns/ext/yahttp/yahttp/cookie.hpp b/pdns/ext/yahttp/yahttp/cookie.hpp new file mode 100644 index 000000000..10070d1d5 --- /dev/null +++ b/pdns/ext/yahttp/yahttp/cookie.hpp @@ -0,0 +1,134 @@ +namespace YaHTTP { + /*! Implements a single cookie */ + class Cookie { + public: + Cookie() { + secure = false; + httponly = false; + name = value = ""; + }; //!< Set the cookie to empty value + + Cookie(const Cookie &rhs) { + domain = rhs.domain; + path = rhs.path; + secure = rhs.secure; + httponly = rhs.httponly; + name = rhs.name; + value = rhs.value; + }; //0) + oss << "; domain=" << domain; + if (path.size()>0) + oss << "; path=" << path; + if (secure) + oss << "; secure"; + if (httponly) + oss << "; httpOnly"; + return oss.str(); + }; //!< Stringify the cookie + }; + + class CookieJar { + public: + std::map cookies; + + CookieJar() {}; + CookieJar(const CookieJar & rhs) { + this->cookies = rhs.cookies; + } + + void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value) { + size_t pos; + pos = keyvalue.find("="); + if (pos == std::string::npos) throw "Not a Key-Value pair (cookie)"; + key = std::string(keyvalue.begin(), keyvalue.begin()+pos); + value = std::string(keyvalue.begin()+pos+1, keyvalue.end()); + } + + void parseCookieHeader(const std::string &cookiestr) { + std::list cookies; + int cstate = 0; //cookiestate + size_t pos,npos; + pos = 0; + cstate = 0; + while(pos < cookiestr.size()) { + if (cookiestr.compare(pos, 7, "expires") ==0 || + cookiestr.compare(pos, 6, "domain") ==0 || + cookiestr.compare(pos, 4, "path") ==0) { + cstate = 1; + // get the date + std::string key, value, s; + npos = cookiestr.find("; ", pos); + if (npos == std::string::npos) { + // last value + s = std::string(cookiestr.begin() + pos + 1, cookiestr.end()); + pos = cookiestr.size(); + } else { + s = std::string(cookiestr.begin() + pos + 1, cookiestr.begin() + npos - 1); + pos = npos+2; + } + keyValuePair(s, key, value); + if (s == "expires") { + DateTime dt; + dt.parseCookie(value); + for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) + i->expires = dt; + } else if (s == "domain") { + for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) + i->domain = value; + } else if (s == "path") { + for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) + i->path = value; + } + } else if (cookiestr.compare(pos, 8, "httpOnly")==0) { + cstate = 1; + for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) + i->httponly = true; + } else if (cookiestr.compare(pos, 6, "secure") ==0) { + cstate = 1; + for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) + i->secure = true; + } else if (cstate == 0) { // expect cookie + Cookie c; + std::string s; + npos = cookiestr.find("; ", pos); + if (npos == std::string::npos) { + // last value + s = std::string(cookiestr.begin() + pos, cookiestr.end()); + pos = cookiestr.size(); + } else { + s = std::string(cookiestr.begin() + pos, cookiestr.begin() + npos); + pos = npos+2; + } + keyValuePair(s, c.name, c.value); + c.name = YaHTTP::Utility::decodeURL(c.name); + c.value = YaHTTP::Utility::decodeURL(c.value); + cookies.push_back(c); + } else if (cstate == 1) { + // ignore crap + break; + } + } + + // store cookies + for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) { + this->cookies[i->name] = *i; + } + }; + }; +}; diff --git a/pdns/ext/yahttp/yahttp/exception.hpp b/pdns/ext/yahttp/yahttp/exception.hpp index 3f0a8fb22..51ff3faf1 100644 --- a/pdns/ext/yahttp/yahttp/exception.hpp +++ b/pdns/ext/yahttp/yahttp/exception.hpp @@ -4,11 +4,11 @@ #include namespace YaHTTP { - class ParseError: public std::exception { + class Error: public std::exception { public: - ParseError() {}; - ParseError(const std::string& reason): reason(reason) {}; - virtual ~ParseError() throw() {}; + Error() {}; + Error(const std::string& reason): reason(reason) {}; + virtual ~Error() throw() {}; virtual const char* what() const throw() { @@ -16,6 +16,11 @@ namespace YaHTTP { } const std::string reason; }; + class ParseError: public YaHTTP::Error { + public: + ParseError() {}; + ParseError(const std::string& reason): Error(reason) {}; + }; }; #endif diff --git a/pdns/ext/yahttp/yahttp/reqresp.cpp b/pdns/ext/yahttp/yahttp/reqresp.cpp index f9b4d24c6..fe51d4523 100644 --- a/pdns/ext/yahttp/yahttp/reqresp.cpp +++ b/pdns/ext/yahttp/yahttp/reqresp.cpp @@ -1,197 +1,101 @@ -#include "reqresp.hpp" +#include "yahttp.hpp" namespace YaHTTP { - - void Request::build(const std::string &method, const std::string &url, const std::string ¶ms) { - this->method = method; - std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper); - this->url.parse(url); - this->headers["host"] = this->url.host; - this->headers["connection"] = "close"; - this->headers["user-agent"] = "yahttp 0.1"; - this->headers["accept"] = "*/*"; - if (params.empty() == false) { - this->headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8"; - this->headers["content-length"] = params.size(); - this->body = params; - } else { - this->body = ""; - } - }; - - Request::Request() {}; - Request::Request(const Response &resp) { - method = resp.method; - url = resp.url; - cookies = resp.cookies; - }; - Request::Request(const Request &req) { - method = req.method; - url = req.url; - parameters = req.parameters; - headers = req.headers; - cookies = req.cookies; - body = req.body; - }; - Request::~Request() {}; - - Response::Response() : status(0) {}; - Response::Response(const Request &req) { - headers["connection"] = "close"; - method = req.method; - url = req.url; - cookies = req.cookies; - status = 200; - }; - Response::Response(const Response &resp) { - method = resp.method; - url = resp.url; - parameters = resp.parameters; - headers = resp.headers; - cookies = resp.cookies; - body = resp.body; - status = resp.status; - statusText = resp.statusText; - }; - Response::~Response() {}; - - void Response::load(std::istream &is) { - AsyncResponseLoader arl(this); - while(is.good()) { - char buf[1024]; - is.read(buf, 1024); - if (is.gcount()) { // did we actually read anything - is.clear(); - if (arl.feed(std::string(buf, is.gcount())) == true) return; // completed - } - } - // FIXME: parse cookies - }; - - void Response::write(std::ostream &os) const { - os << "HTTP/1.1 " << status << " "; - if (statusText.empty()) - os << Utility::status2text(status); - else - os << statusText; - os << "\r\n"; - - // write headers - strstr_map_t::const_iterator iter = headers.begin(); - while(iter != headers.end()) { - os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n"; - iter++; - } - os << "\r\n"; - os << body; - }; - - void Request::load(std::istream &is) { - AsyncRequestLoader arl(this); - while(is.good()) { - char buf[1024]; - is.read(buf, 1024); - if (is.gcount()) { // did we actually read anything - is.clear(); - if (arl.feed(std::string(buf, is.gcount())) == true) return; // completed - } - } - }; - - void Request::write(std::ostream &os) const { - os << method << " " << url.path << " HTTP/1.1" << "\r\n"; - strstr_map_t::const_iterator iter = headers.begin(); - while(iter != headers.end()) { - os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n"; - iter++; - } - os << "\r\n"; - if (body.size()>0) { - os << body; - } - }; - - std::ostream& operator<<(std::ostream& os, const Response &resp) { - resp.write(os); - return os; - }; - - std::istream& operator>>(std::istream& is, Response &resp) { - resp.load(is); - return is; - }; - - std::ostream& operator<<(std::ostream& os, const Request &req) { - req.write(os); - return os; - }; - - std::istream& operator>>(std::istream& is, Request &req) { - req.load(is); - return is; - }; - - bool AsyncRequestLoader::feed(const std::string &somedata) { - size_t pos; - + template + int AsyncLoader::feed(const std::string& somedata) { buffer.append(somedata); while(state < 2) { - // need to find newline in buffer - if ((pos = buffer.find("\r\n")) == std::string::npos) return false; - std::string line(buffer.begin(), buffer.begin()+pos); // exclude CRLF - buffer.erase(buffer.begin(), buffer.begin()+pos+2); // remove line from buffer including CRLF + int cr=0; + // need to find CRLF in buffer + if ((pos = buffer.find_first_of("\n")) == std::string::npos) return false; + if (buffer[pos-1]=='\r') + cr=1; + std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF + buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF + if (state == 0) { // startup line - std::string ver; - std::string tmpurl; - std::istringstream iss(line); - iss >> request->method >> tmpurl >> ver; - if (ver.find("HTTP/1.") != 0) - throw ParseError("Not a HTTP 1.x request"); - // uppercase the request method - std::transform(request->method.begin(), request->method.end(), request->method.begin(), ::toupper); - request->url.parse(tmpurl); - request->parameters = Utility::parseUrlParameters(request->url.parameters); - state = 1; + if (target->kind == YAHTTP_TYPE_REQUEST) { + std::string ver; + std::string tmpurl; + std::istringstream iss(line); + iss >> target->method >> tmpurl >> ver; + if (ver.find("HTTP/1.") != 0) + throw ParseError("Not a HTTP 1.x request"); + // uppercase the target method + std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper); + target->url.parse(tmpurl); + target->getvars = Utility::parseUrlParameters(target->url.parameters); + state = 1; + } else if(target->kind == YAHTTP_TYPE_RESPONSE) { + std::string ver; + std::istringstream iss(line); + iss >> ver >> target->status >> target->statusText; + if (ver.find("HTTP/1.") != 0) + throw ParseError("Not a HTTP 1.x response"); + state = 1; + } } else if (state == 1) { std::string key,value; size_t pos; if (line.empty()) { - chunked = (request->headers.find("transfer-encoding") != request->headers.end() && request->headers["transfer-encoding"] == "chunked"); - // host header is optional - if (request->headers.find("host") != request->headers.end()) - request->url.host = request->headers["host"]; - + chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked"); state = 2; break; } // split headers if ((pos = line.find_first_of(": ")) == std::string::npos) - throw ParseError("Malformed line"); + throw ParseError("Malformed header line"); key = line.substr(0, pos); value = line.substr(pos+2); + Utility::trimRight(value); std::transform(key.begin(), key.end(), key.begin(), ::tolower); - request->headers[key] = value; + // is it already defined + + if ((key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) || + (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST)) { + target->jar.parseCookieHeader(value); + } else { + if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) { + // maybe it contains port? + if ((pos = value.find(":")) == std::string::npos) { + target->url.host = value; + } else { + target->url.host = value.substr(0, pos); + target->url.port = ::atoi(value.substr(pos).c_str()); + } + } + if (target->headers.find(key) != target->headers.end()) { + target->headers[key] = target->headers[key] + ";" + value; + } else { + target->headers[key] = value; + } + } } } - // skip body for GET - if (request->method == "GET") - return true; - - // do we have content-length? + minbody = 0; + // check for expected body size + if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = YAHTTP_MAX_REQUEST_SIZE; + else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = YAHTTP_MAX_RESPONSE_SIZE; + else maxbody = 0; + if (!chunked) { - if (request->headers.find("content-length") != request->headers.end()) { - std::istringstream maxbodyS(request->headers["content-length"]); - maxbodyS >> maxbody; + if (target->headers.find("content-length") != target->headers.end()) { + std::istringstream maxbodyS(target->headers["content-length"]); + maxbodyS >> minbody; + maxbody = minbody; } - if (maxbody < 1) return true; // guess there isn't anything left. - if (maxbody > YAHTTP_MAX_REQUEST_SIZE) - throw ParseError("Request size exceeded"); + if (minbody < 1) return true; // guess there isn't anything left. + if (target->kind == YAHTTP_TYPE_REQUEST && minbody > YAHTTP_MAX_REQUEST_SIZE) throw ParseError("Max request body size exceeded"); + else if (target->kind == YAHTTP_TYPE_RESPONSE && minbody > YAHTTP_MAX_RESPONSE_SIZE) throw ParseError("Max response body size exceeded"); } - if (buffer.size() == 0) return false; + if (maxbody == 0) hasBody = false; + else hasBody = true; - while(buffer.size() > 0 && bodybuf.str().size() < static_cast(maxbody)) { + if (buffer.size() == 0) return ready(); + + while(buffer.size() > 0) { char buf[1024] = {0}; if (chunked) { @@ -212,95 +116,111 @@ namespace YaHTTP { buffer.erase(buffer.begin(), buffer.begin()+chunk_size+1); bodybuf << buf; chunk_size = 0; - if (buffer.size() == 0) return false; // just in case + if (buffer.size() == 0) break; // just in case } } else { - bodybuf << buffer; + if (bodybuf.str().length() + buffer.length() > maxbody) + bodybuf << buffer.substr(0, maxbody - bodybuf.str().length()); + else + bodybuf << buffer; buffer = ""; } } if (chunk_size!=0) return false; // need more data - if (!chunked && bodybuf.str().size() < maxbody) { - return false; // need more data - } - - bodybuf.flush(); - request->body = bodybuf.str(); - bodybuf.str(""); - return true; + return ready(); }; - - bool AsyncResponseLoader::feed(const std::string &somedata) { - size_t pos; - buffer.append(somedata); - while(state < 2) { - // need to find CRLF in buffer - if ((pos = buffer.find("\r\n")) == std::string::npos) return false; - std::string line(buffer.begin(), buffer.begin()+pos); // exclude CRLF - buffer.erase(buffer.begin(), buffer.begin()+pos+2); // remove line from buffer including CRLF - if (state == 0) { // startup line - std::string ver; - std::istringstream iss(line); - iss >> ver >> response->status >> response->statusText; - if (ver.find("HTTP/1.") != 0) - throw ParseError("Not a HTTP 1.x response"); - state = 1; - } else if (state == 1) { - std::string key,value; - size_t pos; - if (line.empty()) { - chunked = (response->headers.find("transfer-encoding") != response->headers.end() && response->headers["transfer-encoding"] == "chunked"); - state = 2; - break; - } - // split headers - if ((pos = line.find_first_of(": ")) == std::string::npos) - throw ParseError("Malformed header line"); - key = line.substr(0, pos); - value = line.substr(pos+2); - std::transform(key.begin(), key.end(), key.begin(), ::tolower); - response->headers[key] = value; + + void HTTPBase::write(std::ostream& os) const { + if (kind == YAHTTP_TYPE_REQUEST) { + std::ostringstream getparmbuf; + std::string getparms; + // prepare URL + for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) { + getparmbuf << Utility::encodeURL(i->first) << "=" << Utility::encodeURL(i->second) << "&"; } + if (getparmbuf.str().length() > 0) + getparms = "?" + std::string(getparmbuf.str().begin(), getparmbuf.str().end() - 1); + else + getparms = ""; + os << method << " " << url.path << getparms << " HTTP/1.1"; + } else if (kind == YAHTTP_TYPE_RESPONSE) { + os << "HTTP/1.1 " << status << " "; + if (statusText.empty()) + os << Utility::status2text(status); + else + os << statusText; } - - if (buffer.size() == 0) return false; - - while(buffer.size() > 0) { - char buf[1024] = {0}; - - if (chunked) { - if (chunk_size == 0) { - // read chunk length - if ((pos = buffer.find('\n')) == std::string::npos) return false; - if (pos > 1023) - throw ParseError("Impossible chunk_size"); - buffer.copy(buf, pos); - buf[pos]=0; // just in case... - buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer - sscanf(buf, "%x", &chunk_size); - if (!chunk_size) break; // last chunk + os << "\r\n"; + + // write headers + strstr_map_t::const_iterator iter = headers.begin(); + while(iter != headers.end()) { + if (iter->first == "host" && kind != YAHTTP_TYPE_REQUEST) { iter++; continue; } + os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n"; + iter++; + } + if (jar.cookies.size() > 0) { // write cookies + for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) { + if (kind == YAHTTP_TYPE_REQUEST) { + os << "Cookie: "; } else { - if (buffer.size() < static_cast(chunk_size+1)) return false; // expect newline - if (buffer.at(chunk_size) != '\n') return false; // there should be newline. - buffer.copy(buf, chunk_size); - buffer.erase(buffer.begin(), buffer.begin()+chunk_size+1); - bodybuf << buf; - chunk_size = 0; - if (buffer.size() == 0) return false; // just in case + os << "Set-Cookie: "; } - } else { - bodybuf << buffer; - buffer = ""; + os << i->second.str() << "\r\n"; } } - - if (chunk_size!=0) return false; // need more data - - bodybuf.flush(); - response->body = bodybuf.str(); - bodybuf.str(""); - return true; + os << "\r\n"; +#ifdef HAVE_CPP_FUNC_PTR + this->renderer(this, os); +#else + os << body; +#endif + }; + + std::ostream& operator<<(std::ostream& os, const Response &resp) { + resp.write(os); + return os; + }; + + std::istream& operator>>(std::istream& is, Response &resp) { + YaHTTP::AsyncResponseLoader arl; + arl.initialize(&resp); + while(is.good()) { + char buf[1024]; + is.read(buf, 1024); + if (is.gcount()) { // did we actually read anything + is.clear(); + if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed + } + } + // throw unless ready + if (arl.ready() == false) + throw ParseError("Was not able to extract a valid Response from stream"); + arl.finalize(); + return is; + }; + + std::ostream& operator<<(std::ostream& os, const Request &req) { + req.write(os); + return os; + }; + + std::istream& operator>>(std::istream& is, Request &req) { + YaHTTP::AsyncRequestLoader arl; + arl.initialize(&req); + while(is.good()) { + char buf[1024]; + is.read(buf, 1024); + if (is.gcount()) { // did we actually read anything + is.clear(); + if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed + } + } + if (arl.ready() == false) + throw ParseError("Was not able to extract a valid Request from stream"); + arl.finalize(); + return is; }; }; diff --git a/pdns/ext/yahttp/yahttp/reqresp.hpp b/pdns/ext/yahttp/yahttp/reqresp.hpp index 0279510d2..91a83cb25 100644 --- a/pdns/ext/yahttp/yahttp/reqresp.hpp +++ b/pdns/ext/yahttp/yahttp/reqresp.hpp @@ -1,108 +1,250 @@ -#include -#include -#include -#include +#ifdef HAVE_CXX11 +#include +#define HAVE_CPP_FUNC_PTR +namespace funcptr = std; +#else +#ifdef HAVE_BOOST +#include +namespace funcptr = boost; +#define HAVE_CPP_FUNC_PTR +#endif +#endif -#include "url.hpp" -#include "utility.hpp" -#include "exception.hpp" +#include +#include -#ifndef YAHTTP_MAX_REQUEST_SIZE +#ifndef WIN32 +#include +#include +#endif + +#ifndef YAHTTP_MAX_REQUEST_SIZE #define YAHTTP_MAX_REQUEST_SIZE 2097152 #endif +#ifndef YAHTTP_MAX_RESPONSE_SIZE +#define YAHTTP_MAX_RESPONSE_SIZE 2097152 +#endif + +#define YAHTTP_TYPE_REQUEST 1 +#define YAHTTP_TYPE_RESPONSE 2 + namespace YaHTTP { typedef std::map strstr_map_t; + typedef std::map strcookie_map_t; - class Response; - class Request; - class AsyncResponseLoader; - class AsyncRequestLoader; + typedef enum { + urlencoded, + multipart + } postformat_t; - class Request { + class HTTPBase { public: - Request(); - Request(const Response &resp); - Request(const Request &req); - ~Request(); - - void build(const std::string &method, const std::string &url, const std::string ¶ms); - - void load(std::istream &is); - void write(std::ostream &os) const; - - strstr_map_t headers; - strstr_map_t parameters; - strstr_map_t cookies; - - URL url; - std::string method; - std::string body; +#ifdef HAVE_CPP_FUNC_PTR + class SendBodyRender { + public: + SendBodyRender() {}; + + size_t operator()(const HTTPBase *doc, std::ostream& os) const { + os << doc->body; + return doc->body.length(); + }; + }; + class SendFileRender { + public: + SendFileRender(const std::string& path) { + this->path = path; + }; + + size_t operator()(const HTTPBase *doc, std::ostream& os) const { + char buf[4096]; + size_t n,k; +#ifdef HAVE_CXX11 + std::ifstream ifs(path, std::ifstream::binary); +#else + std::ifstream ifs(path.c_str(), std::ifstream::binary); +#endif + n = 0; + while(ifs && ifs.good()) { + ifs.read(buf, sizeof buf); + n += (k = ifs.gcount()); + if (k) + os.write(buf, k); + } + + return n; + }; + + std::string path; + }; +#endif + HTTPBase() { +#ifdef HAVE_CPP_FUNC_PTR + renderer = SendBodyRender(); +#endif + }; +protected: + HTTPBase(const HTTPBase& rhs) { + this->url = rhs.url; this->kind = rhs.kind; + this->status = rhs.status; this->statusText = rhs.statusText; + this->method = rhs.method; this->headers = rhs.headers; + this->jar = rhs.jar; this->postvars = rhs.postvars; + this->parameters = rhs.parameters; this->getvars = rhs.getvars; + this->body = rhs.body; +#ifdef HAVE_CPP_FUNC_PTR + this->renderer = rhs.renderer; +#endif + }; + HTTPBase& operator=(const HTTPBase& rhs) { + this->url = rhs.url; this->kind = rhs.kind; + this->status = rhs.status; this->statusText = rhs.statusText; + this->method = rhs.method; this->headers = rhs.headers; + this->jar = rhs.jar; this->postvars = rhs.postvars; + this->parameters = rhs.parameters; this->getvars = rhs.getvars; + this->body = rhs.body; +#ifdef HAVE_CPP_FUNC_PTR + this->renderer = rhs.renderer; +#endif + return *this; + }; +public: + URL url; + int kind; + int status; + std::string statusText; + std::string method; + strstr_map_t headers; + CookieJar jar; + strstr_map_t postvars; + strstr_map_t getvars; +// these two are for Router + strstr_map_t parameters; + std::string routeName; + + std::string body; + +#ifdef HAVE_CPP_FUNC_PTR + funcptr::function renderer; +#endif + void write(std::ostream& os) const; - friend std::istream& operator>>(std::istream& os, const Request &req); - friend std::ostream& operator<<(std::ostream& os, const Request &req); - friend class AsyncRequestLoader; + strstr_map_t& GET() { return getvars; }; + strstr_map_t& POST() { return postvars; }; + strcookie_map_t& COOKIES() { return jar.cookies; }; }; - class Response { + class Response: public HTTPBase { public: - Response(); - Response(const Request &req); - Response(const Response &resp); - ~Response(); - void load(std::istream &is); - void write(std::ostream &os) const; - - strstr_map_t headers; - strstr_map_t parameters; - strstr_map_t cookies; - - URL url; - int status; - std::string statusText; - std::string method; - std::string body; - - friend std::istream& operator>>(std::istream& is, Response &resp); - friend std::ostream& operator<<(std::ostream& os, const Response &resp); - friend class AsyncResponseLoader; + Response() { this->kind = YAHTTP_TYPE_RESPONSE; }; + Response(const HTTPBase& rhs): HTTPBase(rhs) { + this->kind = YAHTTP_TYPE_RESPONSE; + }; + Response& operator=(const HTTPBase& rhs) { + HTTPBase::operator=(rhs); + this->kind = YAHTTP_TYPE_RESPONSE; + return *this; + } + friend std::ostream& operator<<(std::ostream& os, const Response &resp); + friend std::istream& operator>>(std::istream& is, Response &resp); }; - class AsyncResponseLoader { + class Request: public HTTPBase { public: - AsyncResponseLoader(Response *response) { - state = 0; - chunked = false; - chunk_size = 0; - this->response = response; + Request() { this->kind = YAHTTP_TYPE_REQUEST; }; + Request(const HTTPBase& rhs): HTTPBase(rhs) { + this->kind = YAHTTP_TYPE_REQUEST; }; - bool feed(const std::string &somedata); - private: - Response *response; - int state; - std::string buffer; - bool chunked; - int chunk_size; - std::ostringstream bodybuf; + Request& operator=(const HTTPBase& rhs) { + HTTPBase::operator=(rhs); + this->kind = YAHTTP_TYPE_REQUEST; + return *this; + } + + void setup(const std::string& method, const std::string& url) { + this->url.parse(url); + this->headers["host"] = this->url.host; + this->method = method; + std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper); + this->headers["user-agent"] = "YaHTTP v1.0"; + } + + void preparePost(postformat_t format = urlencoded) { + std::ostringstream postbuf; + if (format == urlencoded) { + for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { + postbuf << Utility::encodeURL(i->first) << "=" << Utility::encodeURL(i->second) << "&"; + } + // remove last bit + if (postbuf.str().length()>0) + body = std::string(postbuf.str().begin(), postbuf.str().end()-1); + else + body = ""; + headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8"; + } else if (format == multipart) { + headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543"; + for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { + postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first) << "; charset=UTF-8\r\n\r\n" + << Utility::encodeURL(i->second) << "\r\n"; + } + } + + // set method and change headers + method = "POST"; + headers["content-length"] = body.length(); + }; + + friend std::ostream& operator<<(std::ostream& os, const Request &resp); + friend std::istream& operator>>(std::istream& is, Request &resp); }; - class AsyncRequestLoader { + template + class AsyncLoader { public: - AsyncRequestLoader(Request *request) { - state = 0; - chunked = false; - chunk_size = 0; - maxbody = 0; - this->request = request; - }; - bool feed(const std::string &somedata); - private: - Request *request; + T* target; int state; + size_t pos; + std::string buffer; bool chunked; int chunk_size; std::ostringstream bodybuf; - long maxbody; + size_t maxbody; + size_t minbody; + bool hasBody; + + void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); + + void initialize(T* target) { + chunked = false; chunk_size = 0; + bodybuf.str(""); maxbody = 0; + pos = 0; state = 0; this->target = target; + hasBody = false; + }; + int feed(const std::string& somedata); + bool ready() { return state > 1 && + (!hasBody || + (bodybuf.str().size() <= maxbody && + bodybuf.str().size() >= minbody) + ); + }; + void finalize() { + bodybuf.flush(); + if (ready()) { + strstr_map_t::iterator pos = target->headers.find("content-type"); + if (pos != target->headers.end() && Utility::iequals(pos->second, "application/x-www-form-urlencoded", 32)) { + target->postvars = Utility::parseUrlParameters(bodybuf.str()); + } + target->body = bodybuf.str(); + } + bodybuf.str(""); + this->target = NULL; + }; }; + + class AsyncResponseLoader: public AsyncLoader { + }; + + class AsyncRequestLoader: public AsyncLoader { + }; + }; diff --git a/pdns/ext/yahttp/yahttp/router.cpp b/pdns/ext/yahttp/yahttp/router.cpp new file mode 100644 index 000000000..26cd774f5 --- /dev/null +++ b/pdns/ext/yahttp/yahttp/router.cpp @@ -0,0 +1,163 @@ +/* @file + * @brief Concrete implementation of Router + */ +#include "yahttp.hpp" +#include "router.hpp" + +namespace YaHTTP { + typedef funcptr::tuple TDelim; + + // router is defined here. + YaHTTP::Router Router::router; + + void Router::map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name) { + std::string method2 = method; + bool isopen=false; + // add into vector + for(std::string::const_iterator i = url.begin(); i != url.end(); i++) { + if (*i == '<' && isopen) throw Error("Invalid URL mask, cannot have < after <"); + if (*i == '<') isopen = true; + if (*i == '>' && !isopen) throw Error("Invalid URL mask, cannot have > without < first"); + if (*i == '>') isopen = false; + } + std::transform(method2.begin(), method2.end(), method2.begin(), ::toupper); + routes.push_back(funcptr::make_tuple(method2, url, handler, name)); + }; + + bool Router::route(Request *req, THandlerFunction& handler) { + std::map params; + int pos1,pos2; + std::string pname; + bool matched = false; + std::string rname; + + // iterate routes + for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) { + int k1,k2,k3; + std::string pname; + std::string method, url; + funcptr::tie(method, url, handler, rname) = *i; + + if (method.empty() == false && req->method != method) continue; // no match on method + // see if we can't match the url + params.clear(); + // simple matcher func + for(k1=0, k2=0; k1 < static_cast(url.size()) && k2 < static_cast(req->url.path.size()); ) { + if (url[k1] == '<') { + pos1 = k2; + k3 = k1+1; + // start of parameter + while(k1 < static_cast(url.size()) && url[k1] != '>') k1++; + pname = std::string(url.begin()+k3, url.begin()+k1); + // then we also look it on the url + if (pname[0]=='*') { + pname = pname.substr(1); + // this matches whatever comes after it, basically end of string + pos2 = req->url.path.size(); + matched = true; + if (pname != "") + params[pname] = funcptr::tie(pos1,pos2); + k1 = url.size(); + k2 = req->url.path.size(); + break; + } else { + // match until url[k1] + while(k2 < static_cast(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++; + pos2 = k2; + params[pname] = funcptr::tie(pos1,pos2); + } + k2--; + } + else if (url[k1] != req->url.path[k2]) { + break; + } + + k1++; k2++; + } + + // ensure. + if (url[k1] != req->url.path[k2]) + matched = false; + else + matched = true; + } + + if (!matched) { return false; } // no route + req->parameters.clear(); + + for(std::map::iterator i = params.begin(); i != params.end(); i++) { + int p1,p2; + funcptr::tie(p1,p2) = i->second; + std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2); + value = Utility::decodeURL(value); + req->parameters[i->first] = value; + } + + req->routeName = rname; + + return true; + }; + + void Router::printRoutes(std::ostream &os) { + for(TRouteList::iterator i = routes.begin(); i != routes.end(); i++) { +#ifdef HAVE_CXX11 + std::streamsize ss = os.width(); + std::ios::fmtflags ff = os.setf(std::ios::left); + os.width(10); + os << std::get<0>(*i); + os.width(50); + os << std::get<1>(*i); + os.width(ss); + os.setf(ff); + os << " " << std::get<3>(*i); + os << std::endl; +#else + os << i->get<0>() << " " << i->get<1>() << " " << i->get<3>() << std::endl; +#endif + } + }; + + std::pair Router::urlFor(const std::string &name, const strstr_map_t& arguments) { + std::ostringstream path; + std::string mask,method,result; + int k1,k2,k3; + + bool found = false; + for(TRouteList::iterator i = routes.begin(); !found && i != routes.end(); i++) { +#ifdef HAVE_CXX11 + if (std::get<3>(*i) == name) { mask = std::get<1>(*i); method = std::get<0>(*i); found = true; } +#else + if (i->get<3>() == name) { mask = i->get<1>(); method = i->get<0>(); found = true; } +#endif + } + + if (!found) + throw Error("Route not found"); + + for(k1=0,k3=0;k1(mask.size());k1++) { + if (mask[k1] == '<') { + std::string pname; + strstr_map_t::const_iterator pptr; + k2=k1; + while(k1(mask.size()) && mask[k1]!='>') k1++; + path << mask.substr(k3,k2-k3); + if (mask[k2+1] == '*') + pname = std::string(mask.begin() + k2 + 2, mask.begin() + k1); + else + pname = std::string(mask.begin() + k2 + 1, mask.begin() + k1); + if ((pptr = arguments.find(pname)) != arguments.end()) + path << Utility::encodeURL(pptr->second); + k3 = k1+1; + } + else if (mask[k1] == '*') { + // ready + k3++; + continue; + } + } + std::cout << mask.substr(k3) << std::endl; + path << mask.substr(k3); + result = path.str(); + return std::make_pair(method, result); + } +}; diff --git a/pdns/ext/yahttp/yahttp/router.hpp b/pdns/ext/yahttp/yahttp/router.hpp new file mode 100644 index 000000000..1115af7d3 --- /dev/null +++ b/pdns/ext/yahttp/yahttp/router.hpp @@ -0,0 +1,74 @@ +#ifndef _YAHTTP_ROUTER_HPP +#define _YAHTTP_ROUTER_HPP 1 +/* @file + * @brief Defines router class and support structures + */ +#ifdef HAVE_CXX11 +#include +#include +#define HAVE_CPP_FUNC_PTR +#define IGNORE std::ignore +namespace funcptr = std; +#else +#ifdef HAVE_BOOST +#include +#include +#define IGNORE boost::tuples::ignore +namespace funcptr = boost; +#define HAVE_CPP_FUNC_PTR +#else +#warn "You need to configure with boost or have C++11 capable compiler for router" +#endif +#endif + +#ifdef HAVE_CPP_FUNC_PTR +#include +#include + +namespace YaHTTP { + typedef funcptr::function THandlerFunction; //!< Handler function pointer + typedef funcptr::tuple TRoute; //!< Route tuple (method, urlmask, handler, name) + typedef std::vector TRouteList; //!< List of routes in order of evaluation + + /*! Implements simple router. + +This class implements a router for masked urls. The URL mask syntax is as of follows + +//url/. + +You can use <*param> to denote that everything will be matched and consumed into the parameter, including slash (/). Use <*> to denote that URL +is consumed but not stored. Note that only path is matched, scheme, host and url parameters are ignored. + */ + class Router { + private: + Router() {}; + static Router router; // urlFor(const std::string &name, const strstr_map_t& arguments); //url.path + static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; // URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //= url.size()) return true; // no data if (url[pos] != '/') return false; // not an url if ( (pos1 = url.find_first_of("?", pos)) == std::string::npos ) { - path = url; + path = url.substr(pos); pos = url.size(); } else { path = url.substr(pos, pos1-pos); pos = pos1; } - path = Utility::decodeURL(path); return true; } @@ -87,6 +86,7 @@ namespace YaHTTP { parameters = url.substr(pos+1, pos1-pos-1); pos = pos1; } + if (parameters.size()>0 && *(parameters.end()-1) == '&') parameters.resize(parameters.size()-1); return true; } @@ -97,6 +97,10 @@ namespace YaHTTP { return true; } + void initialize() { + protocol = ""; host = ""; port = 0; username = ""; password = ""; path = ""; parameters = ""; anchor =""; pathless = true; + } + public: std::string to_string() const { std::string tmp; @@ -122,7 +126,7 @@ namespace YaHTTP { port > 0) oss << ":" << port; - oss << Utility::encodeURL(path, true); + oss << path; if (parameters.empty() == false) { if (!pathless) oss << "?"; @@ -143,7 +147,7 @@ namespace YaHTTP { std::string anchor; bool pathless; - URL() { protocol = ""; host = ""; port = 0; username = ""; password = ""; path = ""; parameters = ""; anchor =""; pathless = true; }; + URL() { initialize(); }; URL(const std::string& url) { parse(url); }; @@ -154,9 +158,7 @@ namespace YaHTTP { bool parse(const std::string& url) { // setup - protocol = ""; host = ""; port = 0; - username = ""; password = ""; path = ""; - parameters = ""; anchor =""; pathless = true; + initialize(); if (url.size() > YAHTTP_MAX_URL_LENGTH) return false; size_t pos = 0; diff --git a/pdns/ext/yahttp/yahttp/utility.hpp b/pdns/ext/yahttp/yahttp/utility.hpp index ceb1dad3c..b6cfd3033 100644 --- a/pdns/ext/yahttp/yahttp/utility.hpp +++ b/pdns/ext/yahttp/yahttp/utility.hpp @@ -1,11 +1,150 @@ #ifndef _YAHTTP_UTILITY_HPP #define _YAHTTP_UTILITY_HPP 1 -#include -#include -#include - namespace YaHTTP { + static const char *MONTHS[] = {0,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",0}; + static const char *DAYS[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat",0}; + + class DateTime { + public: + bool isSet; + + int year; + + int month; + int day; + int wday; + + int hours; + int minutes; + int seconds; + + int utc_offset; + + DateTime() { + initialize(); + }; + + void initialize() { + isSet = false; + year = month = day = wday = hours = minutes = seconds = utc_offset = 0; + month = 1; // it's invalid otherwise + }; + + void setLocal() { + fromLocaltime(time((time_t*)NULL)); + }; + + void setGm() { + fromGmtime(time((time_t*)NULL)); + } + + void fromLocaltime(time_t t) { +#ifdef HAVE_LOCALTIME_R + struct tm tm; + localtime_r(&t, &tm); + fromTm(&tm); +#else + struct tm *tm; + tm = localtime(&t); + fromTm(tm); +#endif + }; + + void fromGmtime(time_t t) { +#ifdef HAVE_GMTIME_R + struct tm tm; + gmtime_r(&t, &tm); + fromTm(&tm); +#else + struct tm *tm; + tm = gmtime(&t); + fromTm(tm); +#endif + }; + + void fromTm(const struct tm *tm) { + year = tm->tm_year + 1900; + month = tm->tm_mon + 1; + day = tm->tm_mday; + hours = tm->tm_hour; + minutes = tm->tm_min; + seconds = tm->tm_sec; + wday = tm->tm_wday; + utc_offset = tm->tm_gmtoff; + isSet = true; + }; + + void validate() const { + if (wday < 0 || wday > 6) throw "Invalid date"; + if (month < 1 || month > 12) throw "Invalid date"; + if (year < 0) throw "Invalid date"; + if (hours < 0 || hours > 23 || + minutes < 0 || minutes > 59 || + seconds < 0 || seconds > 60) throw "Invalid date"; + } + + std::string rfc_str() const { + std::ostringstream oss; + validate(); + oss << DAYS[wday] << ", " << std::setfill('0') << std::setw(2) << day << " " << MONTHS[month] << " " << + std::setfill('0') << std::setw(2) << year << " " << + std::setfill('0') << std::setw(2) << hours << ":" << + std::setfill('0') << std::setw(2) << minutes << ":" << + std::setfill('0') << std::setw(2) << seconds << " "; + if (utc_offset>=0) oss << "+"; + else oss << "-"; + int tmp_off = ( utc_offset < 0 ? utc_offset*-1 : utc_offset ); + oss << std::setfill('0') << std::setw(2) << (tmp_off/3600); + oss << std::setfill('0') << std::setw(2) << (tmp_off%3600)/60; + + return oss.str(); + }; + + std::string cookie_str() const { + std::ostringstream oss; + validate(); + oss << std::setfill('0') << std::setw(2) << day << "-" << MONTHS[month] << "-" << year << " " << + std::setfill('0') << std::setw(2) << hours << ":" << + std::setfill('0') << std::setw(2) << minutes << ":" << + std::setfill('0') << std::setw(2) << seconds << " GMT"; + return oss.str(); + } + + void parse822(const std::string &rfc822_date) { + char *pos; + struct tm tm; + if ( (pos = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T %z", &tm)) != NULL) { + fromTm(&tm); + } else { + throw "Unparseable date"; + } + }; + + void parseCookie(const std::string &cookie_date) { + char *pos; + struct tm tm; + if ( (pos = strptime(cookie_date.c_str(), "%d-%b-%Y %T %Z", &tm)) != NULL) { + fromTm(&tm); + } else { + throw "Unparseable date"; + } + }; + + int unixtime() const { + struct tm tm; + tm.tm_year = year-1900; + tm.tm_mon = month-1; + tm.tm_mday = day; + tm.tm_hour = hours; + tm.tm_min = minutes; + tm.tm_sec = seconds; + tm.tm_gmtoff = utc_offset; + return mktime(&tm); + } + + }; + class Utility { public: static std::string decodeURL(const std::string& component) { @@ -37,15 +176,16 @@ namespace YaHTTP { return result; }; - static std::string encodeURL(const std::string& component, bool encodeSlash = true) { + static std::string encodeURL(const std::string& component, bool asUrl = true) { std::string result = component; + std::string skip = "+-.:,&;_#%[]?/@(){}="; char repl[3]; size_t pos; for(std::string::iterator iter = result.begin(); iter != result.end(); iter++) { - if (*iter != '+' && !(encodeSlash == false || *iter == '/') && !std::isalnum(*iter)) { + if (!std::isalnum(*iter) && (!asUrl || skip.find(*iter) == std::string::npos)) { // replace with different thing pos = std::distance(result.begin(), iter); - ::snprintf(repl,3,"%02x", *iter); + std::snprintf(repl,3,"%02x", static_cast(*iter)); result = result.replace(pos, 1, "%", 1).insert(pos+1, repl, 2); iter = result.begin() + pos + 2; } @@ -53,6 +193,24 @@ namespace YaHTTP { return result; }; + static std::string encodeURL(const std::wstring& component, bool asUrl = true) { + unsigned char const *p = reinterpret_cast(&component[0]); + std::size_t s = component.size() * sizeof((*component.begin())); + std::vector vec(p, p+s); + + std::ostringstream result; + std::string skip = "+-.,&;_#%[]?/@(){}="; + for(std::vector::iterator iter = vec.begin(); iter != vec.end(); iter++) { + if (!std::isalnum((char)*iter) && (!asUrl || skip.find((char)*iter) == std::string::npos)) { + // bit more complex replace + result << "%" << std::hex << std::setw(2) << std::setfill('0') << static_cast(*iter); + } else result << (char)*iter; + } + return result.str(); + }; + + + static std::string status2text(int status) { switch(status) { case 200: @@ -175,7 +333,26 @@ namespace YaHTTP { return parameter_map; }; - static void trim_right(std::string &str) { + static bool iequals(const std::string& a, const std::string& b, size_t length) { + std::string::const_iterator ai, bi; + size_t i; + for(ai = a.begin(), bi = b.begin(), i = 0; ai != a.end() && bi != b.end() && i < length; ai++,bi++,i++) { + if (::toupper(*ai) != ::toupper(*bi)) return false; + } + + if (ai == a.end() && bi == b.end()) return true; + if ((ai == a.end() && bi != b.end()) || + (ai != a.end() && bi == b.end())) return false; + + return ::toupper(*ai) == ::toupper(*bi); + } + + static bool iequals(const std::string& a, const std::string& b) { + if (a.size() != b.size()) return false; + return iequals(a,b,a.size()); + } + + static void trimRight(std::string &str) { const std::locale &loc = std::locale::classic(); std::string::reverse_iterator iter = str.rbegin(); while(iter != str.rend() && std::isspace(*iter, loc)) iter++; diff --git a/pdns/ext/yahttp/yahttp/yahttp-config.h b/pdns/ext/yahttp/yahttp/yahttp-config.h new file mode 100644 index 000000000..8241f201f --- /dev/null +++ b/pdns/ext/yahttp/yahttp/yahttp-config.h @@ -0,0 +1,2 @@ +/* config header for embedded libyahttp */ +#define HAVE_BOOST 1 diff --git a/pdns/ext/yahttp/yahttp/yahttp.hpp b/pdns/ext/yahttp/yahttp/yahttp.hpp index 8ab0458a6..d93398645 100644 --- a/pdns/ext/yahttp/yahttp/yahttp.hpp +++ b/pdns/ext/yahttp/yahttp/yahttp.hpp @@ -1,3 +1,36 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "yahttp-config.h" +#include "url.hpp" #include "utility.hpp" +#include "exception.hpp" #include "url.hpp" +#include "cookie.hpp" #include "reqresp.hpp" + +/*! \mainpage Yet Another HTTP Library Documentation +\section sec_quick_start Quick start example + +@code +#include + +int main(void) { + std::ifstream ifs("request.txt"); + YaHTTP::Request req; + ifs >> req; + + std::cout << req.method " " << req.url.path << std::endl; + return 0; +} +@endcode +\author Aki Tuomi +*/ -- 2.40.0