From b30a4af1d67a192609adcbd0296ecc46a5428bb8 Mon Sep 17 00:00:00 2001 From: Zakaria El Orche Date: Wed, 16 Sep 2026 15:54:10 +0000 Subject: [PATCH] feat(core): add request helpers for security flows --- .../flash/exceptions/HttpException.java | 7 ++++- .../java/dev/relism/flash/models/Request.java | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/flash/src/main/java/dev/relism/flash/exceptions/HttpException.java b/flash/src/main/java/dev/relism/flash/exceptions/HttpException.java index a7084b5..9dda050 100644 --- a/flash/src/main/java/dev/relism/flash/exceptions/HttpException.java +++ b/flash/src/main/java/dev/relism/flash/exceptions/HttpException.java @@ -32,7 +32,12 @@ public class HttpException extends RuntimeException { } public static HttpException forbidden() { - return new HttpException(403, "Forbidden"); + return forbidden("Forbidden"); + } + + /** Refused with a reason the caller may be told — why the credential is not enough, never what it would take. */ + public static HttpException forbidden(String message) { + return new HttpException(403, message); } public static HttpException notFound(String what) { diff --git a/flash/src/main/java/dev/relism/flash/models/Request.java b/flash/src/main/java/dev/relism/flash/models/Request.java index b7b0f03..9c45280 100644 --- a/flash/src/main/java/dev/relism/flash/models/Request.java +++ b/flash/src/main/java/dev/relism/flash/models/Request.java @@ -194,6 +194,35 @@ public class Request { */ public List headers() { checkActive(); return requestLine.getHeaders().all(); } + /** + * The scheme and authority the client addressed, e.g. {@code https://example.com}: from + * {@code X-Forwarded-Proto}/{@code X-Forwarded-Host} when present, otherwise the connection and + * the {@code Host} header. Only meaningful behind a proxy that sets or strips those headers. + */ + public String origin() { + String proto = header("X-Forwarded-Proto"); + String host = header("X-Forwarded-Host"); + return (proto != null ? proto : isSecure() ? "https" : "http") + "://" + (host != null ? host : header("Host")); + } + + /** + * Returns the value of cookie {@code name}, or {@code null} if the request does not carry it. + * Reads the {@code Cookie} header in place; only the returned value is allocated. + */ + public String cookie(String name) { + String cookies = header("Cookie"); + if (cookies == null) return null; + for (int at = cookies.indexOf(name); at >= 0; at = cookies.indexOf(name, at + 1)) { + int eq = at + name.length(); + if (eq < cookies.length() && cookies.charAt(eq) == '=' + && (at == 0 || cookies.charAt(at - 1) == ' ' || cookies.charAt(at - 1) == ';')) { + int end = cookies.indexOf(';', eq + 1); + return cookies.substring(eq + 1, end < 0 ? cookies.length() : end); + } + } + return null; + } + // ── Path parameters ─────────────────────────────────────────────────────── /**