From c318aa3eb167e84109f0407e48222a8445b0d0c4 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 27 May 2026 15:38:12 +0200 Subject: [PATCH 1/2] handler for WellKnown Folder --- .../cms/server/configs/SiteHandlerModule.java | 17 +++ .../cms/server/handler/AbstractHandler.java | 12 ++ .../cms/server/handler/StaticFileHandler.java | 9 -- .../cms/server/handler/WellKnownHandler.java | 141 ++++++++++++++++++ .../com/condation/cms/server/host/VHost.java | 2 + 5 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 cms-server/src/main/java/com/condation/cms/server/handler/WellKnownHandler.java diff --git a/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java b/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java index c88b2f0e..4b8078cc 100644 --- a/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java +++ b/cms-server/src/main/java/com/condation/cms/server/configs/SiteHandlerModule.java @@ -43,6 +43,7 @@ import com.condation.cms.server.filter.InitRequestContextFilter; import com.condation.cms.server.filter.PreviewFilter; import com.condation.cms.server.handler.StaticFileHandler; +import com.condation.cms.server.handler.WellKnownHandler; import com.condation.cms.server.handler.auth.JettyAuthenticationHandler; import com.condation.cms.server.handler.content.JettyContentHandler; import com.condation.cms.server.handler.content.JettyTaxonomyHandler; @@ -57,6 +58,7 @@ import com.google.inject.Provides; import com.google.inject.Singleton; import com.google.inject.name.Named; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; @@ -132,4 +134,19 @@ public ResourceHandler assetsHandler (@Named("assets") Path assetBase, ServerPro public StaticFileHandler publicHandler (@Named("public") Path publicBase, ServerProperties serverProperties) throws IOException { return new StaticFileHandler(List.of(publicBase)); } + + @Provides + @Singleton + public WellKnownHandler wellKnownHandler (@Named("public") Path publicBase, Theme theme) throws IOException { + + List paths = new ArrayList<>(); + paths.add(publicBase); + paths.add(theme.publicPath()); + + if (theme.getParentTheme() != null) { + paths.add(theme.getParentTheme().publicPath()); + } + + return new WellKnownHandler(List.of(publicBase)); + } } diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/AbstractHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/AbstractHandler.java index 7acec0ca..3b7234a2 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/AbstractHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/AbstractHandler.java @@ -24,6 +24,7 @@ import com.condation.cms.api.Constants; import com.condation.cms.api.feature.features.IsPreviewFeature; import com.condation.cms.api.request.RequestContext; +import com.condation.cms.api.utils.RequestUtil; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; @@ -37,4 +38,15 @@ protected boolean isPreview(Request request) { var requestContext = (RequestContext) request.getAttribute(Constants.REQUEST_CONTEXT_ATTRIBUTE_NAME); return requestContext.has(IsPreviewFeature.class); } + + protected String getRelativePath(Request request) { + String path = request.getHttpURI().getPath(); + String contextPath = RequestUtil.getContextPath(request); + + if (!contextPath.endsWith("/")) { + contextPath += "/"; + } + + return path.replaceFirst("^" + contextPath, ""); + } } diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/StaticFileHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/StaticFileHandler.java index 9493904f..390b8311 100644 --- a/cms-server/src/main/java/com/condation/cms/server/handler/StaticFileHandler.java +++ b/cms-server/src/main/java/com/condation/cms/server/handler/StaticFileHandler.java @@ -102,16 +102,7 @@ public void failed(Throwable x) { } } - private String getRelativePath(Request request) { - String path = request.getHttpURI().getPath(); - String contextPath = RequestUtil.getContextPath(request); - if (!contextPath.endsWith("/")) { - contextPath += "/"; - } - - return path.replaceFirst("^" + contextPath, ""); - } private String guessContentType(Path path) { try { diff --git a/cms-server/src/main/java/com/condation/cms/server/handler/WellKnownHandler.java b/cms-server/src/main/java/com/condation/cms/server/handler/WellKnownHandler.java new file mode 100644 index 00000000..1db499c3 --- /dev/null +++ b/cms-server/src/main/java/com/condation/cms/server/handler/WellKnownHandler.java @@ -0,0 +1,141 @@ +package com.condation.cms.server.handler; + +/*- + * #%L + * CMS Server + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import com.condation.cms.api.utils.PathUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +@Slf4j +public class WellKnownHandler extends AbstractHandler { + + private final List bases; + + public WellKnownHandler(List bases) { + this.bases = bases; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + + String relativePath = getRelativePath(request); + + if (!relativePath.startsWith(".well-known/")) { + return false; + } + + if (relativePath.contains("..")) { + response.setStatus(400); + callback.succeeded(); + return true; + } + + for (Path base : bases) { + Path requested = base.resolve(relativePath).normalize(); + + if (!Files.exists(requested) + || !Files.isRegularFile(requested) + || !PathUtil.isChild(base, requested)) { + continue; + } + + return serveFile(request, response, callback, requested); + } + // no found + response.setStatus(404); + callback.succeeded(); + + return true; + } + + private boolean serveFile(Request request, Response response, Callback callback, Path requested) { + + try { + response.setStatus(200); + // HEAD support + if ("HEAD".equalsIgnoreCase(request.getMethod())) { + callback.succeeded(); + return true; + } + + long size = Files.size(requested); + response.getHeaders().put("Content-Type", guessContentType(requested)); + response.getHeaders().put("Content-Length", String.valueOf(size)); + + var in = Files.newInputStream(requested); + + Content.copy(Content.Source.from(in), response, new Callback() { + @Override + public void succeeded() { + try { + in.close(); + } catch (IOException ignore) { + } + callback.succeeded(); + } + + @Override + public void failed(Throwable x) { + try { + in.close(); + } catch (IOException ignore) { + } + callback.failed(x); + } + }); + + return true; + + } catch (IOException e) { + log.debug("Error serving .well-known file {}", requested, e); + callback.failed(e); + return true; + } + } + + private String guessContentType(Path path) { + try { + String type = Files.probeContentType(path); + if (type != null) { + return type; + } + + String p = path.toString(); + if (p.endsWith(".json")) { + return "application/json"; + } + if (p.endsWith(".txt")) { + return "text/plain"; + } + + return "application/octet-stream"; + } catch (IOException e) { + return "application/octet-stream"; + } + } +} diff --git a/cms-server/src/main/java/com/condation/cms/server/host/VHost.java b/cms-server/src/main/java/com/condation/cms/server/host/VHost.java index 0229c5d4..d846d2f7 100644 --- a/cms-server/src/main/java/com/condation/cms/server/host/VHost.java +++ b/cms-server/src/main/java/com/condation/cms/server/host/VHost.java @@ -93,6 +93,7 @@ import com.condation.cms.server.annotations.Eager; import com.condation.cms.server.filter.metrics.PipelineRequestMetricsFilter; import com.condation.cms.server.filter.metrics.RequestMetricsFilter; +import com.condation.cms.server.handler.WellKnownHandler; import io.micrometer.core.instrument.MeterRegistry; /** @@ -269,6 +270,7 @@ public Handler buildHttpHandler() { var defaultHandlerSequence = new Handler.Sequence( authHandler, publicHandlerSequence, + injector.getInstance(WellKnownHandler.class), initContextHandler, uiPreviewFilter, routesHandler, From fe460fdcdb83a110e5832221084c44951031f717 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 27 May 2026 15:45:01 +0200 Subject: [PATCH 2/2] example well-knwon file --- test-server/hosts/demo/public/.well-known/security.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-server/hosts/demo/public/.well-known/security.txt diff --git a/test-server/hosts/demo/public/.well-known/security.txt b/test-server/hosts/demo/public/.well-known/security.txt new file mode 100644 index 00000000..27bdbf6f --- /dev/null +++ b/test-server/hosts/demo/public/.well-known/security.txt @@ -0,0 +1 @@ +CondationCMS is secure \ No newline at end of file