]> granicus.if.org Git - pdns/commitdiff
Sync embedded yahttp copy
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 9 Jun 2014 13:28:31 +0000 (15:28 +0200)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Tue, 24 Jun 2014 11:09:26 +0000 (13:09 +0200)
cmouse/yahttp@e8b4f1493e7c6a659102878c9b02fa7f1701464b

Including automake integration from @cmouse.

17 files changed:
configure.ac
pdns/Makefile-recursor
pdns/ext/yahttp/.gitignore
pdns/ext/yahttp/Makefile [deleted file]
pdns/ext/yahttp/Makefile.am [new file with mode: 0644]
pdns/ext/yahttp/yahttp/Makefile [deleted file]
pdns/ext/yahttp/yahttp/Makefile.am [new file with mode: 0644]
pdns/ext/yahttp/yahttp/cookie.hpp [new file with mode: 0644]
pdns/ext/yahttp/yahttp/exception.hpp
pdns/ext/yahttp/yahttp/reqresp.cpp
pdns/ext/yahttp/yahttp/reqresp.hpp
pdns/ext/yahttp/yahttp/router.cpp [new file with mode: 0644]
pdns/ext/yahttp/yahttp/router.hpp [new file with mode: 0644]
pdns/ext/yahttp/yahttp/url.hpp
pdns/ext/yahttp/yahttp/utility.hpp
pdns/ext/yahttp/yahttp/yahttp-config.h [new file with mode: 0644]
pdns/ext/yahttp/yahttp/yahttp.hpp

index 84dec815232ee90363c96cba4bd383c051749f60..22045699059376cfad21d3e9026abfde04b4752a 100644 (file)
@@ -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
index b06087d15c4f49ca8a4b011f3d316944097ffb32..722788aec7d965457b55e47d5dd47d45dd901a76 100644 (file)
@@ -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 \
index 69059d61c5e811cf565269a31757f7f9a29ae627..3ef00e8b3defbaa764de1c83184f0e709804a8de 100644 (file)
@@ -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 (file)
index c9fe151..0000000
+++ /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 (file)
index 0000000..f7da84a
--- /dev/null
@@ -0,0 +1 @@
+SUBDIRS=yahttp
diff --git a/pdns/ext/yahttp/yahttp/Makefile b/pdns/ext/yahttp/yahttp/Makefile
deleted file mode 100644 (file)
index d95e498..0000000
+++ /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 (file)
index 0000000..2595854
--- /dev/null
@@ -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 (file)
index 0000000..10070d1
--- /dev/null
@@ -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;
+     }; //<! Copy cookie values
+
+     DateTime expires; /*!< Expiration date */
+     std::string domain; /*!< Domain where cookie is valid */
+     std::string path; /*!< Path where the cookie is valid */
+     bool httponly; /*!< Whether the cookie is for server use only */
+     bool secure; /*!< Whether the cookie is for HTTPS only */
+     std::string name; /*!< Cookie name */
+     std::string value; /*!< Cookie value */
+
+     std::string str() const {
+       std::ostringstream oss;
+       oss << YaHTTP::Utility::encodeURL(name) << "=" << YaHTTP::Utility::encodeURL(value);
+       if (expires.isSet) 
+         oss << "; expires=" << expires.cookie_str();
+       if (domain.size()>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<std::string, Cookie> 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<Cookie> 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<Cookie>::iterator i = cookies.begin(); i != cookies.end(); i++)
+              i->expires = dt;
+          } else if (s == "domain") {
+            for(std::list<Cookie>::iterator i = cookies.begin(); i != cookies.end(); i++)
+              i->domain = value;
+          } else if (s == "path") {
+            for(std::list<Cookie>::iterator i = cookies.begin(); i != cookies.end(); i++)
+              i->path = value;
+          }
+        } else if (cookiestr.compare(pos, 8, "httpOnly")==0) {
+          cstate = 1;
+          for(std::list<Cookie>::iterator i = cookies.begin(); i != cookies.end(); i++)
+            i->httponly = true;
+        } else if (cookiestr.compare(pos, 6, "secure")  ==0) {
+          cstate = 1;
+          for(std::list<Cookie>::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<Cookie>::iterator i = cookies.begin(); i != cookies.end(); i++) {
+        this->cookies[i->name] = *i;
+      }
+    };
+  };
+};
index 3f0a8fb22ebc90af630c3c453853267bf7444619..51ff3faf18314183ee296df16bddd5a1d3fe3f49 100644 (file)
@@ -4,11 +4,11 @@
 #include <exception>
 
 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
index f9b4d24c6a2f5a4b0410efd8ffd1b49e16a72125..fe51d4523d92cbf76a21135248a706be86a5d60d 100644 (file)
-#include "reqresp.hpp"
+#include "yahttp.hpp"
 
 namespace YaHTTP {
-
-  void Request::build(const std::string &method, const std::string &url, const std::string &params) {
-    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 <class T>
+  int AsyncLoader<T>::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<size_t>(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<size_t>(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;
   };
 };
index 0279510d285f255875fddc33554cfa83c765412d..91a83cb2521494f1272295a781200f080deafe16 100644 (file)
-#include <map>
-#include <iostream>
-#include <locale>
-#include <algorithm>
+#ifdef HAVE_CXX11
+#include <functional>
+#define HAVE_CPP_FUNC_PTR
+namespace funcptr = std;
+#else
+#ifdef HAVE_BOOST
+#include <boost/function.hpp>
+namespace funcptr = boost;
+#define HAVE_CPP_FUNC_PTR
+#endif
+#endif
 
-#include "url.hpp"
-#include "utility.hpp"
-#include "exception.hpp"
+#include <fstream>
+#include <cctype>
 
-#ifndef YAHTTP_MAX_REQUEST_SIZE 
+#ifndef WIN32
+#include <cstdio>
+#include <unistd.h>
+#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<std::string,std::string> strstr_map_t;
+  typedef std::map<std::string,Cookie> 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 &params);
-
-     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<size_t(const HTTPBase*,std::ostream&)> 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 T>
+  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<Response> {
+  };
+
+  class AsyncRequestLoader: public AsyncLoader<Request> {
+  };
+
 };
diff --git a/pdns/ext/yahttp/yahttp/router.cpp b/pdns/ext/yahttp/yahttp/router.cpp
new file mode 100644 (file)
index 0000000..26cd774
--- /dev/null
@@ -0,0 +1,163 @@
+/* @file
+ * @brief Concrete implementation of Router 
+ */
+#include "yahttp.hpp"
+#include "router.hpp"
+
+namespace YaHTTP {
+  typedef funcptr::tuple<int,int> 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<std::string, TDelim> 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<int>(url.size()) && k2 < static_cast<int>(req->url.path.size()); ) {
+        if (url[k1] == '<') {
+          pos1 = k2;
+          k3 = k1+1;
+          // start of parameter
+          while(k1 < static_cast<int>(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<int>(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<std::string, TDelim>::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<std::string,std::string> 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<static_cast<int>(mask.size());k1++) {
+      if (mask[k1] == '<') {
+        std::string pname;
+        strstr_map_t::const_iterator pptr;
+        k2=k1;
+        while(k1<static_cast<int>(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 (file)
index 0000000..1115af7
--- /dev/null
@@ -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 <functional>
+#include <tuple>
+#define HAVE_CPP_FUNC_PTR
+#define IGNORE std::ignore
+namespace funcptr = std;
+#else
+#ifdef HAVE_BOOST
+#include <boost/function.hpp>
+#include <boost/tuple/tuple.hpp>
+#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 <vector>
+#include <utility>
+
+namespace YaHTTP {
+  typedef funcptr::function <void(Request* req, Response* resp)> THandlerFunction; //!< Handler function pointer 
+  typedef funcptr::tuple<std::string, std::string, THandlerFunction, std::string> TRoute; //!< Route tuple (method, urlmask, handler, name)
+  typedef std::vector<TRoute> 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
+
+/<masked>/url<number>/<hi>.<format>
+
+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; //<! Singleton instance of Router
+  public:
+    void map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name); //<! Instance method for mapping urls
+    bool route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
+    void printRoutes(std::ostream &os); //<! Instance method for printing routes
+    std::pair<std::string, std::string> urlFor(const std::string &name, const strstr_map_t& arguments); //<! Instance method for generating paths
+
+/*! Map an URL.
+If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs 
+*/
+    static void Map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map(method, url, handler, name); }; 
+    static void Get(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("GET", url, handler, name); }; //<! Helper for mapping GET
+    static void Post(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("POST", url, handler, name); }; //<! Helper for mapping POST
+    static void Put(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PUT", url, handler, name); }; //<! Helper for mapping PUT
+    static void Patch(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PATCH", url, handler, name); }; //<! Helper for mapping PATCH
+    static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, handler, name); }; //<! Helper for mapping DELETE
+    static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, handler, name); }; //<! Helper for mapping any method
+
+    static bool Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path 
+    static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; //<! Prints all known routes to given output stream
+
+    static std::pair<std::string, std::string> URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //<! Generates url from named route and arguments. Missing arguments are assumed empty
+    static const TRouteList& GetRoutes() { return router.routes; } //<! Reference to route list 
+
+    TRouteList routes; //<! Instance variable for routes
+  };
+};
+#endif
+
+#endif
index 732e8e617b5b46b3bd05ea57b29ab1793465f2c4..e649c59fb6406fa07444edc3baf9a17823fff6fc 100644 (file)
@@ -65,13 +65,12 @@ namespace YaHTTP {
           if (pos >= 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;
index ceb1dad3c9da31ec693f7ef31736aa3c4ca626d3..b6cfd30331fa1911eafee07827cc2e91cddcaae0 100644 (file)
 #ifndef _YAHTTP_UTILITY_HPP
 #define _YAHTTP_UTILITY_HPP 1
 
-#include <string>
-#include <algorithm>
-#include <cstdio>
-
 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<unsigned char>(*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<unsigned char const*>(&component[0]);
+      std::size_t s = component.size() * sizeof((*component.begin()));
+      std::vector<unsigned char> vec(p, p+s);
+
+      std::ostringstream result;
+      std::string skip = "+-.,&;_#%[]?/@(){}=";
+      for(std::vector<unsigned char>::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<unsigned int>(*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 (file)
index 0000000..8241f20
--- /dev/null
@@ -0,0 +1,2 @@
+/* config header for embedded libyahttp */
+#define HAVE_BOOST 1
index 8ab0458a6823838e732a7dc5b6b2ae5a6a9ffde3..d933986457f916e529ff8c179288acb960c6d682 100644 (file)
@@ -1,3 +1,36 @@
+#include <map>
+#include <iostream>
+#include <locale>
+#include <algorithm>
+#include <string>
+#include <cstdio>
+#include <sys/time.h>
+#include <iomanip>
+#include <list>
+#include <vector>
+
+#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 <yahttp/yahttp.hpp>
+
+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
+*/