From: Christian Hofstaedtler Date: Mon, 21 Oct 2013 19:24:26 +0000 (+0200) Subject: switch to yahttp in auth X-Git-Tag: rec-3.6.0-rc1~354^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=80d59cd1693ee201b127da97737d3474f88fec83;p=pdns switch to yahttp in auth A bit more intrusive than needed, but now everybody has a HttpResponse object. --- diff --git a/pdns/session.cc b/pdns/session.cc index 5ec778a0f..1fe586b6b 100644 --- a/pdns/session.cc +++ b/pdns/session.cc @@ -33,63 +33,43 @@ 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= (int)bytes) - { - ret = string(rdbuf + rdoffset, bytes); - bytes -= ret.length(); - rdoffset += ret.length(); - } - - if(bytes) { - scoped_array 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<<":"<>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<(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: "< 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 variables; - if(parts.size()>1) { - stringtok(variables,parts[1],"&"); - } - - for(vector::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); - vectorcparts; - stringtok(cparts,plain,":"); - // L< 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: "<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("" + e.what() + "

" + e.what() + "

"); + resp.headers["Content-Type"] = "text/html; charset=utf-8"; + resp.body = "" + what + "

" + what + "

"; } 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< #include #include +#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 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(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 pathArgs; - map 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 HandlerFunction; + typedef boost::function HandlerFunction; struct HandlerRegistration { std::list urlParts; std::list paramNames; diff --git a/pdns/ws.cc b/pdns/ws.cc index ccf141a2f..8346f3e9f 100644 --- a/pdns/ws.cc +++ b/pdns/ws.cc @@ -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) "
"<
"<queryArgs["ring"].empty()) { + if(req->parameters["ring"].empty()) { vectorentries=S.listRings(); for(vector::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<<""<"<© 2013 PowerDNS.COM BV."<"<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 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 object; object["number"]=lexical_cast(number); - //cerr<<"Flushed cache for '"<body = returnJSONObject(object); + return; } else if(command == "pdns-control") { if(req->method!="POST") throw HttpMethodNotAllowedException(); // cout<<"post: "<(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: '"< 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 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()); @@ -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 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 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 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 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 handler) { +void StatWebServer::registerApiHandler(const string& url, boost::function 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; }"<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(); } diff --git a/pdns/ws.hh b/pdns/ws.hh index 85ae97570..fc89f01d3 100644 --- a/pdns/ws.hh +++ b/pdns/ws.hh @@ -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 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 handler); void printvars(ostringstream &ret); void printargs(ostringstream &ret); void launch();