package sola.documentation.boundary; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import sola.documentation.Requirement; import java.io.IOException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.util.Locale; import static sola.documentation.Requirement.Rn.*; /** Optional public projection of a reviewed build; owner APIs retain their own authentication. */ @Path("/") @jakarta.enterprise.context.ApplicationScoped public class DocumentationResource { @ConfigProperty(name="sola.documentation.root", defaultValue="/app/documentation") String directory; /// @requirement R1.1 serve the published homepage without a login @GET @Requirement(R1_1) public Response home() { return serve("index.html"); } /// @requirement R1.2 serve a published artifact with its media type /// @requirement R2.1 refuse escape paths and unpublished files @GET @Path("docs/{path: .+}") @Requirement({R1_2, R2_1}) public Response artifact(@PathParam("path") String path) { return serve(path); } /// @requirement R3.1 identify the documentation build and its evidence @GET @Path("api/docs/manifest") @Requirement(R3_1) public Response manifest() { return serve("manifest.json"); } private Response serve(String requested) { try { if (requested == null || requested.indexOf('\\') >= 0 || requested.indexOf('\0') >= 0) throw new NotFoundException(); java.nio.file.Path root = java.nio.file.Path.of(directory).toRealPath(); java.nio.file.Path candidate = root.resolve(requested).normalize(); if (!candidate.startsWith(root) || !Files.isRegularFile(candidate) || !candidate.toRealPath().startsWith(root)) throw new NotFoundException(); String media = mediaType(candidate.getFileName().toString()); if (media == null) throw new NotFoundException(); return Response.ok(Files.readAllBytes(candidate), media) .header("X-Content-Type-Options", "nosniff") .header("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; frame-ancestors 'none'") .build(); } catch (IOException | InvalidPathException e) { throw new NotFoundException(); } } private static String mediaType(String filename) { String name = filename.toLowerCase(Locale.ROOT); if (name.endsWith(".html")) return "text/html; charset=utf-8"; if (name.endsWith(".pdf")) return "application/pdf"; if (name.endsWith(".json")) return "application/json"; if (name.endsWith(".md") || name.endsWith(".java")) return "text/plain; charset=utf-8"; return null; } }