]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
OPTIONS handling
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 27 Nov 2024 14:45:48 +0000 (15:45 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 11 Feb 2025 15:28:22 +0000 (16:28 +0100)
pdns/recursordist/settings/rust/src/web.rs
pdns/recursordist/ws-recursor.cc

index 17eb746ad82c462bd19bda0cf4fef6d55531b44f..16b762b532cfc5bba8a467b6bd7cb04534d01ef4 100644 (file)
@@ -180,6 +180,122 @@ struct Context {
     counter: Mutex<u32>,
 }
 
+fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response)
+{
+    let mut uripath = path;
+    if uripath == "/" {
+        uripath = "/index.html";
+    }
+    let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath);
+    if pos.is_none() {
+        eprintln!("{} {} not found", method, uripath);
+    }
+
+    if rustweb::serveStuff(request, response).is_err() {
+        // Return 404 not found response.
+        response.status = StatusCode::NOT_FOUND.as_u16();
+        response.body = NOTFOUND.to_vec();
+        eprintln!("{} {} not found case 2", method, uripath);
+    }
+}
+
+type FileFunc = fn(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response);
+
+fn matcher(method: &Method, path: &str, apifunc: &mut Option<Func>, rawfunc: &mut Option<Func>, filefunc: &mut Option<FileFunc>, allow_password: &mut bool, request: &mut rustweb::Request)
+{
+    let path: Vec<_> = path.split('/').skip(1).collect();
+    match (method, &*path) {
+        (&Method::GET, ["jsonstat"]) => {
+            *allow_password = true;
+            *apifunc = Some(rustweb::jsonstat);
+        }
+        (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) =>
+            *apifunc = Some(rustweb::apiServerCacheFlush),
+        (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) =>
+            *apifunc = Some(rustweb::apiServerConfigAllowFromPUT),
+        (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) =>
+            *apifunc = Some(rustweb::apiServerConfigAllowFromGET),
+        (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) =>
+            *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT),
+        (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) =>
+            *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET),
+        (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) =>
+            *apifunc = Some(rustweb::apiServerConfig),
+        (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) =>
+            *apifunc = Some(rustweb::apiServerRPZStats),
+        (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) =>
+            *apifunc = Some(rustweb::apiServerSearchData),
+        (&Method::GET, ["api", "v1", "servers", "localhost", "zones", id]) => {
+            request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)});
+            *apifunc = Some(rustweb::apiServerZoneDetailGET);
+        }
+        (&Method::PUT, ["api", "v1", "servers", "localhost", "zones", id]) => {
+            request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)});
+            *apifunc = Some(rustweb::apiServerZoneDetailPUT);
+        }
+        (&Method::DELETE, ["api", "v1", "servers", "localhost", "zones", id]) => {
+            request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)});
+            *apifunc = Some(rustweb::apiServerZoneDetailDELETE);
+        }
+        (&Method::GET, ["api", "v1", "servers", "localhost", "statistics"]) => {
+            *allow_password = true;
+            *apifunc = Some(rustweb::apiServerStatistics);
+        }
+        (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) =>
+            *apifunc = Some(rustweb::apiServerZonesGET),
+        (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) =>
+            *apifunc = Some(rustweb::apiServerZonesPOST),
+        (&Method::GET, ["api", "v1", "servers", "localhost"]) => {
+            *allow_password = true;
+            *apifunc = Some(rustweb::apiServerDetail);
+        }
+        (&Method::GET, ["api", "v1", "servers"]) =>
+            *apifunc = Some(rustweb::apiServer),
+        (&Method::GET, ["api", "v1"]) =>
+            *apifunc = Some(rustweb::apiDiscoveryV1),
+        (&Method::GET, ["api"]) =>
+            *apifunc = Some(rustweb::apiDiscovery),
+        (&Method::GET, ["metrics"]) =>
+            *rawfunc = Some(rustweb::prometheusMetrics),
+        _ =>
+            *filefunc = Some(file),
+    }
+}
+
+fn collect_options(path: &str, response: &mut rustweb::Response)
+{
+    let mut methods = vec!();
+    for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] {
+        let mut apifunc: Option<Func> = None;
+        let mut rawfunc: Option<_> = None;
+        let mut filefunc: Option<_> = None;
+        let mut allow_password = false;
+        let mut request = rustweb::Request {
+            body: vec![],
+            uri: String::from(""),
+            vars: vec![],
+            parameters: vec![],
+        };
+        println!("MATCH? {}", path);
+        matcher(&method, path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request);
+        if apifunc.is_some() || rawfunc.is_some() /* || filefunc.is_some() */ {
+            println!("MATCH");
+            methods.push(method.to_string());
+        }
+    }
+    if methods.is_empty() {
+        response.status = 404;
+        return;
+    }
+    response.status = 200;
+    methods.push(Method::OPTIONS.to_string());
+    response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-origin"), value: String::from("*")});
+    response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-headers"), value: String::from("Content-Type, X-API-Key")});
+    response.headers.push(rustweb::KeyValue{key: String::from("access-control-max-age"), value: String::from("3600")});
+    response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-methods"), value: methods.join(", ")});
+    response.headers.push(rustweb::KeyValue{key: String::from("content-type"), value: String::from("text/plain")});
+}
+
 async fn hello(
     rust_request: Request<IncomingBody>,
     ctx: Arc<Context>
@@ -214,97 +330,44 @@ async fn hello(
         headers: vec![],
     };
     let mut apifunc: Option<Func> = None;
+    let mut rawfunc: Option<_> = None;
+    let mut filefunc: Option<_> = None;
     let method = rust_request.method().to_owned();
-    let path: Vec<_> = rust_request.uri().path().split('/').skip(1).collect();
     let mut allow_password = false;
-    match (&method, &*path) {
-        (&Method::GET, ["jsonstat"]) => {
-            allow_password = true;
-            apifunc = Some(rustweb::jsonstat);
-        }
-        (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) =>
-            apifunc = Some(rustweb::apiServerCacheFlush),
-        (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) =>
-            apifunc = Some(rustweb::apiServerConfigAllowFromPUT),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) =>
-            apifunc = Some(rustweb::apiServerConfigAllowFromGET),
-        (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) =>
-            apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) =>
-            apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) =>
-            apifunc = Some(rustweb::apiServerConfig),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) =>
-            apifunc = Some(rustweb::apiServerRPZStats),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) =>
-            apifunc = Some(rustweb::apiServerSearchData),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "zones", id]) => {
-            request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)});
-            apifunc = Some(rustweb::apiServerZoneDetailGET);
-        }
-        (&Method::PUT, ["api", "v1", "servers", "localhost", "zones", id]) => {
-            request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)});
-            apifunc = Some(rustweb::apiServerZoneDetailPUT);
-        }
-        (&Method::DELETE, ["api", "v1", "servers", "localhost", "zones", id]) => {
-            request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)});
-            apifunc = Some(rustweb::apiServerZoneDetailDELETE);
-        }
-        (&Method::GET, ["api", "v1", "servers", "localhost", "statistics"]) => {
-            allow_password = true;
-            apifunc = Some(rustweb::apiServerStatistics);
-        }
-        (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) =>
-            apifunc = Some(rustweb::apiServerZonesGET),
-        (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) =>
-            apifunc = Some(rustweb::apiServerZonesPOST),
-        (&Method::GET, ["api", "v1", "servers", "localhost"]) => {
-            allow_password = true;
-            apifunc = Some(rustweb::apiServerDetail);
-        }
-        (&Method::GET, ["api", "v1", "servers"]) =>
-            apifunc = Some(rustweb::apiServer),
-        (&Method::GET, ["api", "v1"]) =>
-            apifunc = Some(rustweb::apiDiscoveryV1),
-        (&Method::GET, ["api"]) =>
-            apifunc = Some(rustweb::apiDiscovery),
-        (&Method::GET, ["metrics"]) =>
-            rustweb::prometheusMetrics(&request, &mut response).unwrap(),
-        _ => {
-            let mut uripath = rust_request.uri().path();
-            if uripath == "/" {
-                uripath = "/index.html";
-            }
-            let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath);
-            if pos.is_none() {
-                eprintln!("{} {} not found", rust_request.method(), uripath);
+    let mut rust_response = Response::builder();
+
+    if method == &Method::OPTIONS {
+        collect_options(rust_request.uri().path(), &mut response);
+    }
+    else{
+        matcher(&method, rust_request.uri().path(), &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request);
+
+        if let Some(func) = apifunc {
+            let reqheaders = rust_request.headers().clone();
+            if rust_request.method()== Method::POST || rust_request.method() == Method::PUT {
+                request.body = rust_request.collect().await?.to_bytes().to_vec();
             }
-            if rustweb::serveStuff(&request, &mut response).is_err() {
-                // Return 404 not found response.
-                response.status = StatusCode::NOT_FOUND.as_u16();
-                response.body = NOTFOUND.to_vec();
-                eprintln!("{} {} not found case 2", rust_request.method(), uripath);
+            api_wrapper(
+                &ctx,
+                func,
+                &request,
+                &mut response,
+                &reqheaders,
+                rust_response.headers_mut().expect("no headers?"),
+                allow_password,
+            );
+        }
+        else if let Some(func) = rawfunc {
+            if func(&request, &mut response).is_err() {
+                let status =  StatusCode::UNPROCESSABLE_ENTITY; // 422
+                response.status = status.as_u16();
+                response.body = status.canonical_reason().unwrap().as_bytes().to_vec();
             }
         }
-    }
-    let mut rust_response = Response::builder();
-
-    if let Some(func) = apifunc {
-        let reqheaders = rust_request.headers().clone();
-        if rust_request.method()== Method::POST || rust_request.method() == Method::PUT {
-            request.body = rust_request.collect().await?.to_bytes().to_vec();
+        else if let Some(func) = filefunc {
+            func(&ctx, &method, rust_request.uri().path(), &request, &mut response);
         }
-        api_wrapper(
-            &ctx,
-            func,
-            &request,
-            &mut response,
-            &reqheaders,
-            rust_response.headers_mut().expect("no headers?"),
-            allow_password,
-        );
     }
-
     let mut body = full(response.body);
     if method == Method::HEAD {
         body = full(vec!());
index 74b9fec81069e7f340b2a0926b589ef34f7980d1..e5ac0e50dfb54e98dcf338f2f0b355047f80f161 100644 (file)
@@ -216,7 +216,7 @@ static void fillZone(const DNSName& zonename, HttpResponse* resp)
     {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"},
     {"servers", servers},
     {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward},
-    //{"notify_allowed", isAllowNotifyForZone(zonename)},
+    {"notify_allowed", isAllowNotifyForZone(zonename)},
     {"records", records}};
 
   resp->setJsonBody(doc);