]> granicus.if.org Git - pdns/commitdiff
switch to yahttp in auth
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 21 Oct 2013 19:24:26 +0000 (21:24 +0200)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Tue, 5 Nov 2013 11:03:57 +0000 (12:03 +0100)
A bit more intrusive than needed, but now everybody has a HttpResponse
object.

pdns/session.cc
pdns/session.hh
pdns/webserver.cc
pdns/webserver.hh
pdns/ws.cc
pdns/ws.hh

index 5ec778a0f473bbb848282b34a03e540c409c9d41..1fe586b6b22e1bf9c378095af1d6c80fc3b98d4b 100644 (file)
 
 void Session::init()
 {
-  d_bufsize=15049; // why?!
-
-  d_verbose=false;
-
-  rdbuf=new char[d_bufsize];  
-  rdoffset=0;
-  wroffset=0;
-}
-
-void Session::beVerbose()
-{
-  d_verbose=true;
+  d_good = true;
 }
 
 Session::Session(int s, struct sockaddr_in r)
 {
   init();
-  remote=r;
-  clisock=s;
+  d_remote=r;
+  d_socket=s;
 }
 
 int Session::close()
 {
   int rc=0;
   
-  if(clisock>=0)
-    rc=Utility::closesocket(clisock);
+  if(d_socket>=0)
+    rc=Utility::closesocket(d_socket);
 
-  clisock=-1;
+  d_socket=-1;
   return rc;
 }
 
 Session::~Session()
 {
-
   /* NOT CLOSING AUTOMATICALLY ANYMORE!
-    if(clisock>=0)
-    ::close(clisock);
+    if(d_socket>=0)
+    ::close(d_socket);
   */  
-
-  delete[] rdbuf;
 }
 
 //! This function makes a deep copy of Session
 Session::Session(const Session &s)
 {
-  d_bufsize=s.d_bufsize;
-
-  init(); // needs d_bufsize, but will reset rdoffset & wroffset
-
-  rdoffset=s.rdoffset;
-  wroffset=s.wroffset;
-  clisock=s.clisock;
-  remote=s.remote;
+  init();
 
-  memcpy(rdbuf,s.rdbuf,d_bufsize);
-}  
+  d_socket=s.d_socket;
+  d_remote=s.d_remote;
+}
 
 void Session::setTimeout(unsigned int seconds)
 {
@@ -97,7 +77,7 @@ void Session::setTimeout(unsigned int seconds)
 }
 
   
-bool Session::putLine(const string &s)
+bool Session::put(const string &s)
 {
   int length=s.length();
   int written=0;
@@ -105,11 +85,11 @@ bool Session::putLine(const string &s)
 
   while(written < length)
     {
-      err=waitForRWData(clisock, false, d_timeout, 0);
+      err=waitForRWData(d_socket, false, d_timeout, 0);
       if(err<=0)
         throw SessionException("nonblocking write failed: "+string(strerror(errno)));
 
-      err = send(clisock, s.c_str() + written, length-written, 0);
+      err = send(d_socket, s.c_str() + written, length-written, 0);
 
       if(err < 0)
         return false;
@@ -120,40 +100,9 @@ bool Session::putLine(const string &s)
   return true;
 }
 
-char *strnchr(char *p, char c, int len)
-{
-  int n;
-  for(n=0;n<len;n++)
-    if(p[n]==c)
-      return p+n;
-  return 0;
-}
-
-string Session::get(unsigned int bytes)
+static int timeoutRead(int s, char *buf, size_t len, int timeout)
 {
-  string ret;
-  if(wroffset - rdoffset >= (int)bytes) 
-  {
-    ret = string(rdbuf + rdoffset, bytes);
-    bytes -= ret.length();
-    rdoffset += ret.length();
-  }
-  
-  if(bytes) {
-    scoped_array<char> buffer(new char[bytes]);
-    int err = read(clisock, &buffer[0], bytes);  // XXX FIXME should be nonblocking
-    if(err < 0)
-      throw SessionException("Error reading bytes from client: "+string(strerror(errno)));
-    if(err != (int)bytes)
-      throw SessionException("Error reading bytes from client: partial read");
-    ret.append(&buffer[0], err);
-  }
-  return ret;
-}
-
-int Session::timeoutRead(int s, char *buf, size_t len)
-{
-  int err = waitForRWData(s, true, d_timeout, 0);
+  int err = waitForRWData(s, true, timeout, 0);
   
   if(!err)
     throw SessionTimeoutException("timeout reading");
@@ -163,120 +112,39 @@ int Session::timeoutRead(int s, char *buf, size_t len)
   return recv(s,buf,len,0);
 }
 
-bool 
-Session::haveLine()
+bool Session::good()
 {
-  return (wroffset!=rdoffset && (strnchr(rdbuf+rdoffset,'\n',wroffset-rdoffset)!=NULL));
+  return d_good;
 }
-        
 
-bool 
-Session::getLine(string &line)
+size_t Session::read(char* buf, size_t len)
 {
   int bytes;
-  char *p;
-
-  int linelength;
-  
-  // read data into a buffer
-  // find first \n, and return that as string, store how far we were
-
-  for(;;)
-    {
-      if(wroffset==rdoffset)
-        {
-          wroffset=rdoffset=0;
-        }
-
-      if(wroffset!=rdoffset && (p=strnchr(rdbuf+rdoffset,'\n',wroffset-rdoffset))) // we have a full line in store, return that 
-        {
-          // from rdbuf+rdoffset to p should become the new line
-
-          linelength=p-(rdbuf+rdoffset); 
-          
-          *p=0; // terminate
-          
-          line=rdbuf+rdoffset;
-          line+="\n";
-          
-          rdoffset+=linelength+1;
-
-          return true;
-        }
-      // we need more data before we can return a line
-
-      if(wroffset==d_bufsize) // buffer is full, flush to left
-        {
-          if(!rdoffset) // line too long!
-            {
-              // FIXME: do stuff
-              close();
-              return false;
-            }
-
-          memmove(rdbuf,rdbuf+rdoffset,wroffset-rdoffset);
-          wroffset-=rdoffset;
-          rdoffset=0;
-        }
-      bytes=timeoutRead(clisock,rdbuf+wroffset,d_bufsize-wroffset);
-
-      if(bytes<0)
-          throw SessionException("error on read from socket: "+string(strerror(errno)));
-
-      if(bytes==0)
-        throw SessionException("Remote closed connection");
-
-      wroffset+=bytes;
-    }
-  // we never get here
-}
-  
-int Session::getSocket()
-{
-  return clisock;
-}
+  bytes = timeoutRead(d_socket, buf, len, d_timeout);
 
-string Session::getRemote ()
-{
-  ostringstream o;
-  uint32_t rint=htonl(remote.sin_addr.s_addr);
-  o<< (rint>>24 & 0xff)<<".";
-  o<< (rint>>16 & 0xff)<<".";
-  o<< (rint>>8  & 0xff)<<".";
-  o<< (rint     & 0xff);
-  o<<":"<<htons(remote.sin_port);
-
-  return o.str();
-}
+  if(bytes<0)
+    throw SessionException("error on read from socket: "+string(strerror(errno)));
 
-uint32_t Session::getRemoteAddr()
-{
+  if(bytes==0)
+    d_good = false;
 
-  return htonl(remote.sin_addr.s_addr);
+  return bytes;
 }
 
-string Session::getRemoteIP()
+int Session::getSocket()
 {
-  ostringstream o;
-  uint32_t rint=htonl(remote.sin_addr.s_addr);
-  o<< (rint>>24 & 0xff)<<".";
-  o<< (rint>>16 & 0xff)<<".";
-  o<< (rint>>8  & 0xff)<<".";
-  o<< (rint     & 0xff);
-
-  return o.str();
+  return d_socket;
 }
-  
 
 Session *Server::accept()
 {
-  struct sockaddr_in remote;
-  Utility::socklen_t len=sizeof(remote);
+  struct sockaddr_in d_remote;
+  Utility::socklen_t len=sizeof(d_remote);
 
-  int clisock=-1;
+  int d_socket=-1;
 
 
-  while((clisock=::accept(s,(struct sockaddr *)(&remote),&len))==-1) // repeat until we have a successful connect
+  while((d_socket=::accept(s,(struct sockaddr *)(&d_remote),&len))==-1) // repeat until we have a successful connect
     {
       //      L<<Logger::Error<<"accept() returned: "<<strerror(errno)<<endl;
       if(errno==EMFILE) {
@@ -285,10 +153,10 @@ Session *Server::accept()
 
     }
 
-  return new Session(clisock, remote);
+  return new Session(d_socket, d_remote);
 }
 
-Server::Server(int port, const string &localaddress)
+Server::Server(const string &localaddress, int port)
 {
   d_local = ComboAddress(localaddress.empty() ? "0.0.0.0" : localaddress, port);
   s = socket(d_local.sin4.sin_family ,SOCK_STREAM,0);
index 96412e06803bc93494f1e76f7a8f859a8b7a8698..81a0ec643761d19e53466a52cf1141c43733a388 100644 (file)
@@ -54,12 +54,9 @@ public:
 class Session
 {
 public:
-  bool getLine(string &); //!< Read a line from the remote
-  bool haveLine(); //!< returns true if a line is available
-  bool putLine(const string &s); //!< Write a line to the remote
-  bool sendFile(int fd); //!< Send a file out
-  int timeoutRead(int s,char *buf, size_t len);
-  string get(unsigned int bytes);
+  bool put(const string &s);
+  bool good();
+  size_t read(char* buf, size_t len);
 
   Session(int s, struct sockaddr_in r); //!< Start a session on an existing socket, and inform this class of the remotes name
 
@@ -67,39 +64,29 @@ public:
       and does a nonblocking connect to support this timeout. It should be noted that nonblocking connects 
       suffer from bad portability problems, so look here if you see weird problems on new platforms */
   Session(const string &remote, int port, int timeout=0); 
-  Session(uint32_t ip, int port, int timeout=0);
 
   Session(const Session &s); 
   
   ~Session();
   int getSocket(); //!< return the filedescriptor for layering violations
-  string getRemote();
-  uint32_t getRemoteAddr();
-  string getRemoteIP();
-  void beVerbose();
   int close(); //!< close and disconnect the connection
   void setTimeout(unsigned int seconds);
 private:
-  void doConnect(uint32_t ip, int port);
-  bool d_verbose;
-  char *rdbuf;
-  int d_bufsize;
-  int rdoffset;
-  int wroffset;
-  int clisock;
-  struct sockaddr_in remote;
+  int d_socket;
+  struct sockaddr_in d_remote;
   void init();
   int d_timeout;
-
+  bool d_good;
 };
 
 //! The server class can be used to create listening servers
 class Server
 {
 public:
-  Server(int p, const string &localaddress=""); //!< port on which to listen
+  Server(const string &localaddress, int port);
   Session* accept(); //!< Call accept() in an endless loop to accept new connections
   ComboAddress d_local;
+
 private:
   int s;
 };
index af667ccc27df424703d6a5681fb3dded044f80b7..516f3a9780ee6a35905982c01563014de62c6754 100644 (file)
@@ -123,154 +123,129 @@ static void *WebServerConnectionThreadStart(void *p) {
   connectionThreadData* data = static_cast<connectionThreadData*>(p);
   pthread_detach(pthread_self());
   data->webServer->serveConnection(data->client);
+
+  data->client->close();
+  delete data->client;
+
   delete data;
+
   return NULL;
 }
 
-void WebServer::serveConnection(Session* client)
-try {
-  HttpRequest req;
+HttpResponse WebServer::handleRequest(HttpRequest req)
+{
+  HttpResponse resp(req);
+  // set default headers
+  resp.headers["Content-Type"] = "text/html; charset=utf-8";
 
   try {
-    string line;
-    client->setTimeout(5);
-    client->getLine(line);
-    stripLine(line);
-    if(line.empty())
-      throw HttpBadRequestException();
-    //    L<<"page: "<<line<<endl;
-
-    vector<string> parts;
-    stringtok(parts, line);
-
-    if(parts.size()>1) {
-      req.method = parts[0];
-      req.uri = parts[1];
-    }
-
-    parts.clear();
-    stringtok(parts,req.uri,"?");
-    req.path = parts[0];
-
-    vector<string> variables;
-    if(parts.size()>1) {
-      stringtok(variables,parts[1],"&");
-    }
-
-    for(vector<string>::const_iterator i=variables.begin();
-        i!=variables.end();++i) {
-
-      parts.clear();
-      stringtok(parts,*i,"=");
-      if(parts.size()>1)
-        req.queryArgs[parts[0]]=parts[1];
-      else
-        req.queryArgs[parts[0]]="";
+    YaHTTP::strstr_map_t::iterator header;
+
+    if ((header = req.headers.find("accept")) != req.headers.end()) {
+      // json wins over html
+      if (header->second.find("application/json") != std::string::npos) {
+        req.accept_json = true;
+      } else if (header->second.find("text/html") != std::string::npos) {
+        req.accept_html = true;
+      }
     }
 
-    bool authOK=0;
-    int postlen = 0;
-    // read & ignore other lines
-    do {
-      client->getLine(line);
-      stripLine(line);
-
-      if(line.empty())
-        break;
-
-      size_t colon = line.find(":");
-      if(colon==std::string::npos)
-        throw HttpBadRequestException();
-
-      string header = toLower(line.substr(0, colon));
-      string value = line.substr(line.find_first_not_of(' ', colon+1));
+    if (!d_password.empty()) {
+      // validate password
+      header = req.headers.find("authorization");
+      bool auth_ok = false;
+      if (header != req.headers.end() && toLower(header->second).find("basic ") == 0) {
+        string cookie = header->second.substr(6);
 
-      if(header == "authorization" && toLower(value).find("basic ") == 0) {
-        string cookie=value.substr(6);
         string plain;
+        B64Decode(cookie, plain);
 
-        B64Decode(cookie,plain);
-        vector<string>cparts;
-        stringtok(cparts,plain,":");
-        // L<<Logger::Error<<"Entered password: '"<<cparts[1].c_str()<<"', should be '"<<d_password.c_str()<<"'"<<endl;
-        if(cparts.size()==2 && !strcmp(cparts[1].c_str(),d_password.c_str())) { // this gets rid of terminating zeros
-          authOK=1;
-        }
+        vector<string> cparts;
+        stringtok(cparts, plain, ":");
+
+        // this gets rid of terminating zeros
+        auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), d_password.c_str())));
       }
-      else if(header == "content-length" && req.method=="POST") {
-        postlen = atoi(value.c_str());
-//        cout<<"Got a post: "<<postlen<<" bytes"<<endl;
+      if (!auth_ok) {
+        throw HttpUnauthorizedException();
       }
-      else if(header == "accept") {
-        // json wins over html
-        if(value.find("application/json")!=std::string::npos) {
-          req.accept_json=true;
-        } else if(value.find("text/html")!=std::string::npos) {
-          req.accept_html=true;
-        }
-      }
-      else
-        ; // cerr<<"Ignoring line: "<<line<<endl;
-      
-    } while(true);
-
-    if(postlen) 
-      req.body = client->get(postlen);
-  
-    if(!d_password.empty() && !authOK)
-      throw HttpUnauthorizedException();
+    }
 
     HandlerFunction *handler;
-    if (route(req.path, req.pathArgs, &handler)) {
-      bool custom=false;
-      string ret=(*handler)(&req, &custom);
-
-      if(!custom) {
-        client->putLine("HTTP/1.1 200 OK\n");
-        client->putLine("Connection: close\n");
-        client->putLine("Content-Type: text/html; charset=utf-8\n\n");
-      }
-      client->putLine(ret);
-    } else {
+    if (!route(req.url.path, req.path_parameters, &handler)) {
       throw HttpNotFoundException();
     }
 
+    (*handler)(&req, &resp);
   }
   catch(HttpException &e) {
-    client->putLine(e.statusLine());
-    client->putLine("Connection: close\n");
-    client->putLine(e.headers());
+    resp = e.response();
+    string what = YaHTTP::Utility::status2text(resp.status);
     if(req.accept_html) {
-      client->putLine("Content-Type: text/html; charset=utf-8\n\n");
-      client->putLine("<!html><title>" + e.what() + "</title><h1>" + e.what() + "</h1>");
+      resp.headers["Content-Type"] = "text/html; charset=utf-8";
+      resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
     } else if (req.accept_json) {
-      client->putLine("Content-Type: application/json\n\n");
-      client->putLine(returnJSONError(e.what()));
+      resp.headers["Content-Type"] = "application/json";
+      resp.body = returnJSONError(what);
     } else {
-      client->putLine("Content-Type: text/plain; charset=utf-8\n\n");
-      client->putLine(e.what());
+      resp.headers["Content-Type"] = "text/plain; charset=utf-8";
+      resp.body = what;
     }
   }
 
-  client->close();
-  delete client;
-  client=0;
+  // always set these headers
+  resp.headers["Server"] = "PowerDNS/"VERSION;
+  resp.headers["Connection"] = "close";
+
+  return resp;
+}
+
+void WebServer::serveConnection(Session* client)
+try {
+  HttpRequest req;
+  YaHTTP::AsyncRequestLoader yarl(&req);
+
+  client->setTimeout(5);
+
+  bool complete = false;
+  try {
+    while(client->good()) {
+      int bytes;
+      char buf[1024];
+      bytes = client->read(buf, sizeof(buf));
+      if (bytes) {
+        string data = string(buf, bytes);
+        if (yarl.feed(data)) {
+          complete = true;
+          break;
+        }
+      }
+    }
+  } catch (YaHTTP::ParseError &e) {
+    complete = false;
+  }
+
+  if (!complete) {
+    client->put("HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\nYour Browser sent a request that this server failed to understand.\r\n");
+    return;
+  }
+
+  HttpResponse resp = WebServer::handleRequest(req);
+  ostringstream ss;
+  resp.write(ss);
+  client->put(ss.str());
 }
 catch(SessionTimeoutException &e) {
   // L<<Logger::Error<<"Timeout in webserver"<<endl;
-  return 0;
 }
 catch(PDNSException &e) {
   L<<Logger::Error<<"Exception in webserver: "<<e.reason<<endl;
-  return 0;
 }
 catch(std::exception &e) {
   L<<Logger::Error<<"STL Exception in webserver: "<<e.what()<<endl;
-  return 0;
 }
 catch(...) {
   L<<Logger::Error<<"Unknown exception in webserver"<<endl;
-  return 0;
 }
 
 WebServer::WebServer(const string &listenaddress, int port, const string &password)
@@ -278,9 +253,8 @@ WebServer::WebServer(const string &listenaddress, int port, const string &passwo
   d_listenaddress=listenaddress;
   d_port=port;
   d_password=password;
-  d_server = 0; // on exception, this class becomes a NOOP later on
   try {
-    d_server = new Server(d_port, d_listenaddress);
+    d_server = new Server(d_listenaddress, d_port);
   }
   catch(SessionException &e) {
     L<<Logger::Error<<"Fatal error in webserver: "<<e.reason<<endl;
index bdf49f2abf8f22af92de9f6ebea899f9e4faaae4..ef40f6cb2c2b8ce2d6be413048e616e74ecd9d32 100644 (file)
 #include <map>
 #include <string>
 #include <list>
+#include "yahttp/yahttp.hpp"
 
 #include "namespaces.hh"
 class Server;
 class Session;
 
+class HttpRequest : public YaHTTP::Request {
+public:
+  HttpRequest() : YaHTTP::Request(), accept_json(false), accept_html(false) { };
+
+  map<string,string> path_parameters;
+  bool accept_json;
+  bool accept_html;
+};
+
+class HttpResponse: public YaHTTP::Response {
+public:
+  HttpResponse() : YaHTTP::Response() { };
+  HttpResponse(const YaHTTP::Request &req) : YaHTTP::Response(req) { };
+  HttpResponse(const YaHTTP::Response &resp) : YaHTTP::Response(resp) { };
+};
+
+
 class HttpException
 {
 public:
-  HttpException(int status_code, const std::string& reason_phrase) :
-    d_status_code(status_code), d_reason_phrase(reason_phrase)
+  HttpException(int status) : d_response()
   {
+    d_response.status = status;
   };
 
-  virtual std::string statusLine() const {
-    return "HTTP/1.0 " + lexical_cast<string>(d_status_code) + " " + d_reason_phrase + "\n";
-  }
-
-  virtual std::string headers() const {
-    return "";
-  }
-
-  virtual std::string what() const {
-    return d_reason_phrase;
+  HttpResponse response()
+  {
+    return d_response;
   }
 
-private:
-  int d_status_code;
-  std::string d_reason_phrase;
+protected:
+  HttpResponse d_response;
 };
 
 class HttpBadRequestException : public HttpException {
 public:
-  HttpBadRequestException() : HttpException(400, "Bad Request") { };
+  HttpBadRequestException() : HttpException(400) { };
 };
 
 class HttpUnauthorizedException : public HttpException {
 public:
-  HttpUnauthorizedException() : HttpException(401, "Unauthorized") { };
-
-  std::string headers() const {
-    return "WWW-Authenticate: Basic realm=\"PowerDNS\"\n";
+  HttpUnauthorizedException() : HttpException(401)
+  {
+    d_response.headers["WWW-Authenticate"] = "Basic realm=\"PowerDNS\"";
   }
 };
 
 class HttpNotFoundException : public HttpException {
 public:
-  HttpNotFoundException() : HttpException(404, "Not Found") { };
+  HttpNotFoundException() : HttpException(404) { };
 };
 
 class HttpMethodNotAllowedException : public HttpException {
 public:
-  HttpMethodNotAllowedException() : HttpException(405, "Method Not Allowed") { };
+  HttpMethodNotAllowedException() : HttpException(405) { };
 };
 
-class HttpRequest {
-public:
-  HttpRequest() : accept_json(false), accept_html(false) { };
-
-  string method;
-  string post;
-  string uri;
-  string path;
-  string body;
-  map<string,string> pathArgs;
-  map<string,string> queryArgs;
-  bool accept_json;
-  bool accept_html;
-};
 
 class WebServer
 {
@@ -100,8 +95,9 @@ public:
   void go();
 
   void serveConnection(Session* client);
+  HttpResponse handleRequest(HttpRequest request);
 
-  typedef boost::function<string(HttpRequest* req, bool *custom)> HandlerFunction;
+  typedef boost::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
   struct HandlerRegistration {
     std::list<string> urlParts;
     std::list<string> paramNames;
index ccf141a2faee5347a38f80a060f664c5d638342f..8346f3e9f8563d0e196932a3409886fc3ab19b45 100644 (file)
@@ -37,7 +37,6 @@
 #include "rapidjson/stringbuffer.h"
 #include "rapidjson/writer.h"
 #include "version.hh"
-#include "session.hh"
 
 using namespace rapidjson;
 
@@ -186,17 +185,19 @@ string StatWebServer::makePercentage(const double& val)
   return (boost::format("%.01f%%") % val).str();
 }
 
-string StatWebServer::indexfunction(HttpRequest* req, bool *custom)
+void StatWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
 {
-  if(!req->queryArgs["resetring"].empty()){
-    *custom=true;
-    S.resetRing(req->queryArgs["resetring"]);
-    return "HTTP/1.1 301 Moved Permanently\nLocation: /\nConnection: close\n\n";
+  if(!req->parameters["resetring"].empty()){
+    S.resetRing(req->parameters["resetring"]);
+    resp->status = 301;
+    resp->headers["Location"] = "/";
+    return;
   }
-  if(!req->queryArgs["resizering"].empty()){
-    *custom=true;
-    S.resizeRing(req->queryArgs["resizering"], atoi(req->queryArgs["size"].c_str()));
-    return "HTTP/1.1 301 Moved Permanently\nLocation: /\nConnection: close\n\n";
+  if(!req->parameters["resizering"].empty()){
+    S.resizeRing(req->parameters["resizering"], atoi(req->parameters["size"].c_str()));
+    resp->status = 301;
+    resp->headers["Location"] = "/";
+    return;
   }
 
   ostringstream ret;
@@ -250,7 +251,7 @@ string StatWebServer::indexfunction(HttpRequest* req, bool *custom)
     "<br>"<<endl;
 
   ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
-  if(req->queryArgs["ring"].empty()) {
+  if(req->parameters["ring"].empty()) {
     vector<string>entries=S.listRings();
     for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
       printtable(ret,*i,S.getRingTitle(*i));
@@ -260,13 +261,13 @@ string StatWebServer::indexfunction(HttpRequest* req, bool *custom)
       printargs(ret);
   }
   else
-    printtable(ret,req->queryArgs["ring"],S.getRingTitle(req->queryArgs["ring"]),100);
+    printtable(ret,req->parameters["ring"],S.getRingTitle(req->parameters["ring"]),100);
 
   ret<<"</div></div>"<<endl;
   ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
   ret<<"</body></html>"<<endl;
 
-  return ret.str();
+  resp->body = ret.str();
 }
 
 static int intFromJson(const Value& val) {
@@ -366,7 +367,7 @@ static string createOrUpdateZone(const string& zonename, bool onlyCreate, varmap
   return getZone(zonename);
 }
 
-static string apiServerConfig(HttpRequest* req) {
+static void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
 
@@ -387,17 +388,17 @@ static string apiServerConfig(HttpRequest* req) {
     kv.PushBack(value, doc.GetAllocator());
     doc.PushBack(kv, doc.GetAllocator());
   }
-  return makeStringFromDocument(doc);
+  resp->body = makeStringFromDocument(doc);
 }
 
-static string apiServerSearchLog(HttpRequest* req) {
+static void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
 
-  return makeLogGrepJSON(req->queryArgs["q"], ::arg()["experimental-logfile"], " pdns[");
+  resp->body = makeLogGrepJSON(req->parameters["q"], ::arg()["experimental-logfile"], " pdns[");
 }
 
-static string apiServerZones(HttpRequest* req) {
+static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
 
@@ -429,25 +430,33 @@ static string apiServerZones(HttpRequest* req) {
     jdomains.PushBack(jdi, doc.GetAllocator());
   }
   doc.AddMember("domains", jdomains, doc.GetAllocator());
-  return makeStringFromDocument(doc);
+  resp->body = makeStringFromDocument(doc);
 }
 
-static string jsonDispatch(HttpRequest* req, const string& command) {
+void StatWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
+{
+  string command;
+
+  if(req->parameters.count("command")) {
+    command = req->parameters["command"];
+    req->parameters.erase("command");
+  }
+
   if(command=="get") {
-    if(req->queryArgs.empty()) {
+    if(req->parameters.empty()) {
       vector<string> entries = S.getEntries();
       BOOST_FOREACH(string& ent, entries) {
-        req->queryArgs[ent];
+        req->parameters[ent];
       }
-      req->queryArgs["version"];
-      req->queryArgs["uptime"];
+      req->parameters["version"];
+      req->parameters["uptime"];
     }
 
     string variable, value;
     
     Document doc;
     doc.SetObject();
-    for(varmap_t::const_iterator iter = req->queryArgs.begin(); iter != req->queryArgs.end() ; ++iter) {
+    for(varmap_t::const_iterator iter = req->parameters.begin(); iter != req->parameters.end() ; ++iter) {
       variable = iter->first;
       if(variable == "version") {
         value = VERSION;
@@ -461,31 +470,37 @@ static string jsonDispatch(HttpRequest* req, const string& command) {
       jval.SetString(value.c_str(), value.length(), doc.GetAllocator());
       doc.AddMember(variable.c_str(), jval, doc.GetAllocator());
     }
-    return makeStringFromDocument(doc);
+    resp->body = makeStringFromDocument(doc);
+    return;
   }
   else if(command=="config") {
-    return apiServerConfig(req);
+    apiServerConfig(req, resp);
+    return;
   }
   else if(command == "flush-cache") {
     extern PacketCache PC;
     int number; 
-    if(req->queryArgs["domain"].empty())
+    if(req->parameters["domain"].empty())
       number = PC.purge();
     else
-      number = PC.purge(req->queryArgs["domain"]);
+      number = PC.purge(req->parameters["domain"]);
       
     map<string, string> object;
     object["number"]=lexical_cast<string>(number);
-    //cerr<<"Flushed cache for '"<<queryArgs["domain"]<<"', cleaned "<<number<<" records"<<endl;
-    return returnJSONObject(object);
+    //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
+    resp->body = returnJSONObject(object);
+    return;
   }
   else if(command == "pdns-control") {
     if(req->method!="POST")
       throw HttpMethodNotAllowedException();
     // cout<<"post: "<<post<<endl;
     rapidjson::Document document;
-    if(document.Parse<0>(req->body.c_str()).HasParseError())
-      return returnJSONError("Unable to parse JSON");
+    if(document.Parse<0>(req->body.c_str()).HasParseError()) {
+      resp->status = 400;
+      resp->body = returnJSONError("Unable to parse JSON");
+      return;
+    }
     // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
     vector<string> parameters;
     stringtok(parameters, document["parameters"].GetString(), " \t");
@@ -498,20 +513,28 @@ static string jsonDispatch(HttpRequest* req, const string& command) {
     if(ptr) {
       m["result"] = (*ptr)(parameters, 0);
     } else {
+      resp->status = 404;
       m["error"]="No such function "+toUpper(parameters[0]);
     }
-    return returnJSONObject(m);
+    resp->body = returnJSONObject(m);
+    return;
   }
   else if(command == "zone-rest") { // http://jsonstat?command=zone-rest&rest=/powerdns.nl/www.powerdns.nl/a
     vector<string> parts;
-    stringtok(parts, req->queryArgs["rest"], "/");
-    if(parts.size() != 3) 
-      return returnJSONError("Could not parse rest parameter");
+    stringtok(parts, req->parameters["rest"], "/");
+    if(parts.size() != 3) {
+      resp->status = 400;
+      resp->body = returnJSONError("Could not parse rest parameter");
+      return;
+    }
     UeberBackend B;
     SOAData sd;
     sd.db = (DNSBackend*)-1;
-    if(!B.getSOA(parts[0], sd) || !sd.db)
-      return returnJSONError("Could not find domain '"+parts[0]+"'");
+    if(!B.getSOA(parts[0], sd) || !sd.db) {
+      resp->status = 404;
+      resp->body = returnJSONError("Could not find domain '"+parts[0]+"'");
+      return;
+    }
     
     QType qtype;
     qtype=parts[2];
@@ -540,7 +563,8 @@ static string jsonDispatch(HttpRequest* req, const string& command) {
         ret+=returnJSONObject(object);
       }
       ret+="]}";
-      return ret;
+      resp->body = ret;
+      return;
     }
     else if(req->method=="DELETE") {
       sd.db->replaceRRSet(sd.domain_id, qname, qtype, vector<DNSResourceRecord>());
@@ -548,8 +572,11 @@ static string jsonDispatch(HttpRequest* req, const string& command) {
     }
     else if(req->method=="POST") {
       rapidjson::Document document;
-      if(document.Parse<0>(req->body.c_str()).HasParseError())
-        return returnJSONError("Unable to parse JSON");
+      if(document.Parse<0>(req->body.c_str()).HasParseError()) {
+        resp->status = 400;
+        resp->body = returnJSONError("Unable to parse JSON");
+        return;
+      }
       
       DNSResourceRecord rr;
       vector<DNSResourceRecord> rrset;
@@ -575,111 +602,101 @@ static string jsonDispatch(HttpRequest* req, const string& command) {
         }
         catch(std::exception& e) 
         {
-          return returnJSONError("Following record had a problem: "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what());
+          resp->body = returnJSONError("Following record had a problem: "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what());
+          return;
         }
       }
       // but now what
       sd.db->startTransaction(qname);
       sd.db->replaceRRSet(sd.domain_id, qname, qtype, rrset);
       sd.db->commitTransaction();
-      return req->body;
+      resp->body = req->body;
+      return;
     }  
   }
   else if(command == "zone") {
-    string zonename = req->queryArgs["zone"];
-    if (zonename.empty())
-      return returnJSONError("Must give zone parameter");
+    string zonename = req->parameters["zone"];
+    if (zonename.empty()) {
+      resp->status = 400;
+      resp->body = returnJSONError("Must give zone parameter");
+    }
 
     if(req->method == "GET") {
       // get current zone
-      return getZone(zonename);
+      resp->body = getZone(zonename);
+      return;
     } else if (req->method == "POST") {
       // create
-      return createOrUpdateZone(zonename, true, req->queryArgs);
+      resp->body = createOrUpdateZone(zonename, true, req->parameters);
+      return;
     } else if (req->method == "PUT") {
       // update or create
-      return createOrUpdateZone(zonename, false, req->queryArgs);
+      resp->body = createOrUpdateZone(zonename, false, req->parameters);
+      return;
     } else if (req->method == "DELETE") {
       // delete
       UeberBackend B;
       DomainInfo di;
-      if(!B.getDomainInfo(zonename, di))
-        return returnJSONError("Deleting domain '"+zonename+"' failed: domain does not exist");
-      if(!di.backend->deleteDomain(zonename))
-        return returnJSONError("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
+      if(!B.getDomainInfo(zonename, di)) {
+        resp->body = returnJSONError("Deleting domain '"+zonename+"' failed: domain does not exist");
+        return;
+      }
+      if(!di.backend->deleteDomain(zonename)) {
+        resp->body = returnJSONError("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
+        return;
+      }
       map<string, string> success; // empty success object
-      return returnJSONObject(success);
+      resp->body = returnJSONObject(success);
+      return;
     } else {
       throw HttpMethodNotAllowedException();
     }
   }
   else if(command=="log-grep") {
-    return makeLogGrepJSON(req->queryArgs["needle"], ::arg()["experimental-logfile"], " pdns[");
+    resp->body = makeLogGrepJSON(req->parameters["needle"], ::arg()["experimental-logfile"], " pdns[");
+    return;
   }
   else if(command=="domains") {
-    return apiServerZones(req);
+    apiServerZones(req, resp);
+    return;
   }
 
-  return returnJSONError("No or unknown command given");
+  resp->body = returnJSONError("No or unknown command given");
+  resp->status = 404;
+  return;
 }
 
-static string apiWrapper(boost::function<string(HttpRequest*)> handler, HttpRequest* req, bool *custom) {
-  *custom=1; // indicates we build the response
-  string ret="HTTP/1.1 200 OK\r\n"
-  "Server: PowerDNS/"VERSION"\r\n"
-  "Connection: close\r\n"
-  "Access-Control-Allow-Origin: *\r\n"
-  "Content-Type: application/json\r\n"
-  "\r\n" ;
+static void apiWrapper(boost::function<void(HttpRequest*,HttpResponse*)> handler, HttpRequest* req, HttpResponse* resp) {
+  resp->headers["Access-Control-Allow-Origin"] = "*";
+  resp->headers["Content-Type"] = "application/json";
 
   string callback;
 
-  if(req->queryArgs.count("callback")) {
-    callback=req->queryArgs["callback"];
-    req->queryArgs.erase("callback");
+  if(req->parameters.count("callback")) {
+    callback=req->parameters["callback"];
+    req->parameters.erase("callback");
   }
   
-  req->queryArgs.erase("_"); // jQuery cache buster
+  req->parameters.erase("_"); // jQuery cache buster
 
-  if(!callback.empty())
-      ret += callback+"(";
-
-  ret += handler(req);
+  handler(req, resp);
 
   if(!callback.empty()) {
-    ret += ");";
+    resp->body = callback + "(" + resp->body + ");";
   }
-  return ret;
 }
 
-void StatWebServer::registerApiHandler(const string& url, boost::function<string(HttpRequest*)> handler) {
+void StatWebServer::registerApiHandler(const string& url, boost::function<void(HttpRequest*,HttpResponse*)> handler) {
   WebServer::HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2);
   d_ws->registerHandler(url, f);
 }
 
-string StatWebServer::jsonstat(HttpRequest* req)
+void StatWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
 {
-  string command;
+  resp->headers["Cache-Control"] = "max-age=86400";
+  resp->headers["Content-Type"] = "text/css";
 
-  if(req->queryArgs.count("command")) {
-    command=req->queryArgs["command"];
-    req->queryArgs.erase("command");
-  }
-
-  return jsonDispatch(req, command);
-}
-
-string StatWebServer::cssfunction(HttpRequest* req, bool *custom)
-{
-  *custom=1; // indicates we build the response
   ostringstream ret;
-  ret<<"HTTP/1.1 200 OK\r\n"
-  "Server: PowerDNS/"VERSION"\r\n"
-  "Connection: close\r\n"
-  "Cache-Control: max-age=86400\r\n"
-  "Content-Type: text/css\r\n"
-  "\r\n";
-
   ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
   ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
   ret<<"a { color: #0959c2; }"<<endl;
@@ -706,7 +723,7 @@ string StatWebServer::cssfunction(HttpRequest* req, bool *custom)
   ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }"<<endl;
   ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
   ret<<".resizering {float: right;}"<<endl;
-  return ret.str();
+  resp->body = ret.str();
 }
 
 void StatWebServer::launch()
@@ -719,7 +736,7 @@ void StatWebServer::launch()
       registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
       registerApiHandler("/servers/localhost/zones", &apiServerZones);
       // legacy dispatch
-      registerApiHandler("/jsonstat", boost::bind(&StatWebServer::jsonstat, this, _1));
+      registerApiHandler("/jsonstat", boost::bind(&StatWebServer::jsonstat, this, _1, _2));
     }
     d_ws->go();
   }
index 85ae9757022521ccb48c16ffd85a5ab347efe39d..fc89f01d38a649e23dea60a7ce492ade9fd2b419 100644 (file)
@@ -77,6 +77,7 @@ private:
 
 class WebServer;
 class HttpRequest;
+class HttpResponse;
 
 class StatWebServer
 {
@@ -88,10 +89,10 @@ public:
 private:
   static void *threadHelper(void *);
   static void *statThreadHelper(void *p);
-  string indexfunction(HttpRequest* req, bool *custom);
-  string cssfunction(HttpRequest* req, bool *custom);
-  string jsonstat(HttpRequest* req);
-  void registerApiHandler(const string& url, boost::function<string(HttpRequest*)> handler);
+  void indexfunction(HttpRequest* req, HttpResponse* resp);
+  void cssfunction(HttpRequest* req, HttpResponse* resp);
+  void jsonstat(HttpRequest* req, HttpResponse* resp);
+  void registerApiHandler(const string& url, boost::function<void(HttpRequest*, HttpResponse*)> handler);
   void printvars(ostringstream &ret);
   void printargs(ostringstream &ret);
   void launch();