]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Encode symbols in zone names for REST API URLs 1264/head
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Wed, 5 Feb 2014 15:06:44 +0000 (16:06 +0100)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Wed, 5 Feb 2014 15:06:44 +0000 (16:06 +0100)
pdns/ws-api.cc
pdns/ws-api.hh
pdns/ws-auth.cc
pdns/ws-auth.hh
regression-tests.api/test_Zones.py

index b0e097b3c227858ce1cc8c7ef0df600c864241a2..4a25319c9a0f2234dc79012fe81b8e823ded7ef6 100644 (file)
@@ -31,6 +31,7 @@
 #include <string.h>
 #include <ctype.h>
 #include <sys/types.h>
+#include <iomanip>
 
 #ifndef HAVE_STRCASESTR
 
@@ -222,3 +223,61 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
 
   resp->setBody(doc);
 }
+
+string apiZoneIdToName(const string& id) {
+  string zonename;
+  ostringstream ss;
+
+  if(id.empty())
+    throw HttpBadRequestException();
+
+  std::size_t lastpos = 0, pos = 0;
+  while ((pos = id.find('=', lastpos)) != string::npos) {
+    ss << id.substr(lastpos, pos-lastpos);
+    if ((id[pos+1] >= '0' && id[pos+1] <= '9') &&
+        (id[pos+2] >= '0' && id[pos+2] <= '9')) {
+      char c = ((id[pos+1] - '0')*10) + (id[pos+2] - '0');
+      ss << c;
+    } else {
+      throw HttpBadRequestException();
+    }
+
+    lastpos = pos+3;
+  }
+  if (lastpos < pos) {
+    ss << id.substr(lastpos, pos-lastpos);
+  }
+
+  zonename = ss.str();
+
+  // strip trailing dot
+  if (zonename.substr(zonename.size()-1) == ".") {
+    zonename = zonename.substr(0, zonename.size()-1);
+  }
+  return zonename;
+}
+
+string apiZoneNameToId(const string& name) {
+  ostringstream ss;
+
+  for(string::const_iterator iter = name.begin(); iter != name.end(); ++iter) {
+    if ((*iter >= 'A' && *iter <= 'Z') ||
+        (*iter >= 'a' && *iter <= 'z') ||
+        (*iter >= '0' && *iter <= '9') ||
+        (*iter == '.') || (*iter == '-')) {
+      ss << *iter;
+    } else {
+      ss << "=" << std::setfill('0') << std::setw(2) << (int)(*iter);
+    }
+  }
+
+  // add trailing dot
+  string id = ss.str() + ".";
+
+  // special handling for the root zone, as a dot on it's own doesn't work
+  // everywhere.
+  if (id == ".") {
+    id = (boost::format("=%d") % (int)('.')).str();
+  }
+  return id;
+}
index 3f28212e671a2a560cc670c6b600493472439ef2..76cfc522fea8229b4e912be2320e2af1fff04642 100644 (file)
@@ -31,5 +31,9 @@ void apiServerConfig(HttpRequest* req, HttpResponse* resp);
 void apiServerSearchLog(HttpRequest* req, HttpResponse* resp);
 void apiServerStatistics(HttpRequest* req, HttpResponse* resp);
 
+// helpers
+string apiZoneIdToName(const string& id);
+string apiZoneNameToId(const string& name);
+
 // To be provided by product code.
 void productServerStatisticsFetch(std::map<string,string>& out);
index 6935601533e853397f37770085388f820ac5537c..f80b5390332f31324677b44a187075b00552598d 100644 (file)
 #include "rapidjson/writer.h"
 #include "ws-api.hh"
 #include "version.hh"
+#include <iomanip>
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
 
 using namespace rapidjson;
 
@@ -281,8 +286,10 @@ static void fillZone(const string& zonename, HttpResponse* resp) {
   doc.SetObject();
 
   // id is the canonical lookup key, which doesn't actually match the name (in some cases)
-  doc.AddMember("id", di.zone.c_str(), doc.GetAllocator());
-  string url = (boost::format("/servers/localhost/zones/%s") % di.zone).str();
+  string zoneId = apiZoneNameToId(di.zone);
+  Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
+  doc.AddMember("id", jzoneId, doc.GetAllocator());
+  string url = "/servers/localhost/zones/" + zoneId;
   Value jurl(url.c_str(), doc.GetAllocator()); // copy
   doc.AddMember("url", jurl, doc.GetAllocator());
   doc.AddMember("name", di.zone.c_str(), doc.GetAllocator());
@@ -422,8 +429,10 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
     Value jdi;
     jdi.SetObject();
     // id is the canonical lookup key, which doesn't actually match the name (in some cases)
-    jdi.AddMember("id", di.zone.c_str(), doc.GetAllocator());
-    string url = (boost::format("/servers/localhost/zones/%s") % di.zone).str();
+    string zoneId = apiZoneNameToId(di.zone);
+    Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
+    jdi.AddMember("id", jzoneId, doc.GetAllocator());
+    string url = "/servers/localhost/zones/" + zoneId;
     Value jurl(url.c_str(), doc.GetAllocator()); // copy
     jdi.AddMember("url", jurl, doc.GetAllocator());
     jdi.AddMember("name", di.zone.c_str(), doc.GetAllocator());
@@ -444,7 +453,7 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
 }
 
 static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
-  string zonename = req->path_parameters["id"];
+  string zonename = apiZoneIdToName(req->path_parameters["id"]);
 
   if(req->method == "PUT") {
     // update domain settings
@@ -497,7 +506,7 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) {
 
   UeberBackend B;
   DomainInfo di;
-  string zonename = req->path_parameters["id"];
+  string zonename = apiZoneIdToName(req->path_parameters["id"]);
   if(!B.getDomainInfo(zonename, di))
     throw ApiException("Could not find domain '"+zonename+"'");
 
index cf21275dbcd1c0e6941466953017d2406206d0b9..243623b95a59dd5cb22691f4eb9589262942bf6a 100644 (file)
 #include <map>
 #include <time.h>
 #include <pthread.h>
-#include <sstream>
-#include <iomanip>
-#include <unistd.h>
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif // HAVE_CONFIG_H
-
 #include "misc.hh"
 #include "namespaces.hh"
 
index aa5a2a8b73e109537db7afe6b37a7cd250b16bbf..b4a5479fd80b4c54724664edd788b162a516bf3a 100644 (file)
@@ -41,17 +41,26 @@ class Servers(ApiTestCase):
             if k in payload:
                 self.assertEquals(data[k], payload[k])
 
-    @unittest.expectedFailure
     def test_CreateZoneWithSymbols(self):
         payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
         name = payload['name']
-        expected_id = name.replace('/', '\\047')
+        expected_id = (name.replace('/', '=47')) + '.'
         for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
             self.assertIn(k, data)
             if k in payload:
                 self.assertEquals(data[k], payload[k])
         self.assertEquals(data['id'], expected_id)
 
+    def test_GetZoneWithSymbols(self):
+        payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
+        name = payload['name']
+        zone_id = (name.replace('/', '=47')) + '.'
+        r = self.session.get(self.url("/servers/localhost/zones/" + zone_id))
+        for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
+            self.assertIn(k, data)
+            if k in payload:
+                self.assertEquals(data[k], payload[k])
+
     def test_GetZone(self):
         r = self.session.get(self.url("/servers/localhost/zones"))
         domains = r.json()