working on forcing cookies, better header setting/adding, session/userprofile persisting

This commit is contained in:
Jörg Prante 2024-04-18 16:23:52 +02:00
parent e444bfca5a
commit 715ee9cd2a
49 changed files with 384 additions and 399 deletions

View file

@ -1,34 +0,0 @@
package org.xbib.net.http.j2html;
import org.xbib.net.Resource;
import org.xbib.net.http.server.resource.HtmlTemplateResourceHandler;
import org.xbib.net.http.server.route.HttpRouterContext;
import java.io.IOException;
import java.nio.file.Path;
public class J2HtmlAuthResourceHandler extends HtmlTemplateResourceHandler {
public J2HtmlAuthResourceHandler() {
this(null, "java", "index.java");
}
public J2HtmlAuthResourceHandler(Path root, String suffix, String indexFileName) {
super(root, suffix, indexFileName);
}
@Override
protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException {
return httpRouterContext.isAuthenticated() ?
createAuthenticatedResource(httpRouterContext) :
createUnauthenticatedResource(httpRouterContext);
}
protected Resource createAuthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
return new J2HtmlResource(this, httpRouterContext);
}
protected Resource createUnauthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
return new UnauthorizedHandler.UnauthorizedResource(this, httpRouterContext);
}
}

View file

@ -13,6 +13,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
import static org.xbib.j2html.TagCreator.body; import static org.xbib.j2html.TagCreator.body;
import static org.xbib.j2html.TagCreator.document;
import static org.xbib.j2html.TagCreator.h1; import static org.xbib.j2html.TagCreator.h1;
import static org.xbib.j2html.TagCreator.html; import static org.xbib.j2html.TagCreator.html;
import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE; import static org.xbib.net.http.HttpHeaderNames.CONTENT_TYPE;
@ -37,17 +38,13 @@ public class J2HtmlResource extends HtmlTemplateResource {
httpResponseStatus = getResponseStatus(); httpResponseStatus = getResponseStatus();
} }
context.status(httpResponseStatus) context.status(httpResponseStatus)
.header("cache-control", cacheControl()) // override default must-revalidate behavior .setHeader("cache-control", "no-cache")
.header("content-length", Integer.toString(dataBuffer.writePosition())) .setHeader("content-length", Integer.toString(dataBuffer.writePosition()))
.header(CONTENT_TYPE, "text/html; charset=" + getCharset().displayName()) .setHeader(CONTENT_TYPE, "text/html; charset=" + getCharset().displayName())
.body(dataBuffer) .body(dataBuffer)
.done(); .done();
} }
protected String cacheControl() {
return "no-cache";
}
protected HttpResponseStatus getResponseStatus() { protected HttpResponseStatus getResponseStatus() {
return HttpResponseStatus.OK; return HttpResponseStatus.OK;
} }
@ -65,6 +62,6 @@ public class J2HtmlResource extends HtmlTemplateResource {
* @return the body string fo the HTTP response * @return the body string fo the HTTP response
*/ */
protected String renderHtml(HttpRouterContext context) { protected String renderHtml(HttpRouterContext context) {
return html(body(h1("Hello World"))).render(); return document(html(body(h1("Hello World"))));
} }
} }

View file

@ -22,6 +22,12 @@ public class J2HtmlResourceHandler extends HtmlTemplateResourceHandler {
@Override @Override
protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException { protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException {
return httpRouterContext.isAuthenticated() ?
new J2HtmlResource(this, httpRouterContext) :
createUnauthenticatedResource(httpRouterContext);
}
protected Resource createUnauthenticatedResource(HttpRouterContext httpRouterContext) throws IOException {
return new J2HtmlResource(this, httpRouterContext); return new J2HtmlResource(this, httpRouterContext);
} }
} }

View file

@ -61,13 +61,8 @@ public class J2HtmlServiceBuilder extends BaseHttpServiceBuilder {
public J2HtmlService build() { public J2HtmlService build() {
if (handlers == null) { if (handlers == null) {
if (securityDomain != null) { HttpHandler httpHandler = new J2HtmlResourceHandler(prefix, suffix, "index.java");
HttpHandler httpHandler = new J2HtmlAuthResourceHandler(prefix, suffix, "index.java"); setHandler(httpHandler);
setHandler(httpHandler);
} else {
HttpHandler httpHandler = new J2HtmlResourceHandler(prefix, suffix, "index.java");
setHandler(httpHandler);
}
} }
return new J2HtmlService(this); return new J2HtmlService(this);
} }

View file

@ -138,7 +138,7 @@ public final class Bootstrap {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -165,14 +165,14 @@ public final class Bootstrap {
.setPath("/favicon.ico") .setPath("/favicon.ico")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, "image/x-icon") .setHeader(HttpHeaderNames.CONTENT_TYPE, "image/x-icon")
.body(NettyDataBufferFactory.getInstance().wrap(fromHex(hexFavIcon))) .body(NettyDataBufferFactory.getInstance().wrap(fromHex(hexFavIcon)))
.done(); .done();
}) })
.build()) .build())
.addService(BaseHttpService.builder() .addService(BaseHttpService.builder()
.setPath("/webjars/**") .setPath("/webjars/**")
.setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/")) .setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/", 24 + 2600))
.build()) .build())
.addService(httpService) .addService(httpService)
.addService(GroovyTemplateService.builder() .addService(GroovyTemplateService.builder()

View file

@ -11,8 +11,8 @@ import org.xbib.net.http.HttpHeaderValues;
import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.HttpResponseStatus;
import org.xbib.net.http.HttpVersion; import org.xbib.net.http.HttpVersion;
import org.xbib.net.http.j2html.InternalServerErrorHandler; import org.xbib.net.http.j2html.InternalServerErrorHandler;
import org.xbib.net.http.j2html.J2HtmlAuthResourceHandler;
import org.xbib.net.http.j2html.J2HtmlResource; import org.xbib.net.http.j2html.J2HtmlResource;
import org.xbib.net.http.j2html.J2HtmlResourceHandler;
import org.xbib.net.http.j2html.J2HtmlService; import org.xbib.net.http.j2html.J2HtmlService;
import org.xbib.net.http.server.application.web.WebApplication; import org.xbib.net.http.server.application.web.WebApplication;
import org.xbib.net.http.server.auth.BasicAuthenticationHandler; import org.xbib.net.http.server.auth.BasicAuthenticationHandler;
@ -148,20 +148,20 @@ public final class Bootstrap {
.setPath("/favicon.ico") .setPath("/favicon.ico")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, "image/x-icon") .setHeader(HttpHeaderNames.CONTENT_TYPE, "image/x-icon")
.body(NettyDataBufferFactory.getInstance().wrap(fromHex(hexFavIcon))) .body(NettyDataBufferFactory.getInstance().wrap(fromHex(hexFavIcon)))
.done(); .done();
}) })
.build()) .build())
.addService(BaseHttpService.builder() .addService(BaseHttpService.builder()
.setPath("/webjars/**") .setPath("/webjars/**")
.setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/")) .setHandler(new ClassLoaderResourceHandler(Bootstrap.class.getClassLoader(), "META-INF/resources/", 24 * 3600))
.build()) .build())
.addService(BaseHttpService.builder() .addService(BaseHttpService.builder()
.setPath("/insecure") .setPath("/insecure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -203,10 +203,10 @@ public final class Bootstrap {
return 0; return 0;
} }
static class MyResourceHandler extends J2HtmlAuthResourceHandler { static class MyResourceHandler extends J2HtmlResourceHandler {
@Override @Override
protected Resource createAuthenticatedResource(HttpRouterContext httpRouterContext) throws IOException { protected Resource createResource(HttpRouterContext httpRouterContext) throws IOException {
return new DemoResource(this, httpRouterContext); return new DemoResource(this, httpRouterContext);
} }

View file

@ -62,7 +62,7 @@ public class NettyHttps2ServerMultiRequestLoadTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain " + .body("secure domain " +
" SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " + " SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " +

View file

@ -62,7 +62,7 @@ public class NettyHttps2ServerTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain " + .body("secure domain " +
" SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " + " SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " +

View file

@ -64,7 +64,7 @@ public class NettyHttpsServerMultiRequestLoadTest {
.setHandler(ctx -> { .setHandler(ctx -> {
logger.log(Level.INFO, "executing /secure"); logger.log(Level.INFO, "executing /secure");
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -151,7 +151,7 @@ public class NettyHttpsServerMultiRequestLoadTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +

View file

@ -68,7 +68,7 @@ public class NettyHttpsServerTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -146,7 +146,7 @@ public class NettyHttpsServerTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +
@ -223,7 +223,7 @@ public class NettyHttpsServerTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain " + .body("secure domain " +
" SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " + " SNI host " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " " +

View file

@ -51,7 +51,7 @@ public class NettyHttp2ServerMultiRequestLoadTest {
.setPath("/domain") .setPath("/domain")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain: " + .body("domain: " +
" base URL = " + ctx.getRequest().getBaseURL() + " base URL = " + ctx.getRequest().getBaseURL() +

View file

@ -49,7 +49,7 @@ public class NettyHttp2ServerTest {
.setPath("/domain") .setPath("/domain")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("Hello, here is my response: " + .body("Hello, here is my response: " +
ctx.getRequest().asJson()) ctx.getRequest().asJson())

View file

@ -48,7 +48,7 @@ public class NettyHttpServerBodyTest {
.setHandler(ctx -> { .setHandler(ctx -> {
String body = ctx.getRequestBuilder().getBodyAsChars(StandardCharsets.UTF_8).toString(); String body = ctx.getRequestBuilder().getBodyAsChars(StandardCharsets.UTF_8).toString();
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() + .body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() + " local address = " + ctx.getRequest().getLocalAddress() +

View file

@ -58,7 +58,7 @@ class NettyHttpServerByteOrderMarkTest {
String content = ctx.getRequestBuilder().getBodyAsChars(StandardCharsets.UTF_8).toString(); String content = ctx.getRequestBuilder().getBodyAsChars(StandardCharsets.UTF_8).toString();
logger.log(Level.FINEST, "got content = " + content); logger.log(Level.FINEST, "got content = " + content);
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() + .body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() + " local address = " + ctx.getRequest().getLocalAddress() +

View file

@ -53,7 +53,7 @@ public class NettyHttpServerFailureTest {
.setPath("/domain") .setPath("/domain")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain" + .body("domain" +
" parameter = " + ctx.getRequest().getParameter().toString() + " parameter = " + ctx.getRequest().getParameter().toString() +

View file

@ -82,7 +82,7 @@ public class NettyHttpServerFileUploadTest {
.map(m -> StandardCharsets.UTF_8.decode(m.getByteBuffer())) .map(m -> StandardCharsets.UTF_8.decode(m.getByteBuffer()))
.collect(Collectors.joining()); .collect(Collectors.joining());
ctx.status(httpResponseStatus) ctx.status(httpResponseStatus)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() + .body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() + " local address = " + ctx.getRequest().getLocalAddress() +
@ -175,7 +175,7 @@ public class NettyHttpServerFileUploadTest {
} }
} }
ctx.status(httpResponseStatus) ctx.status(httpResponseStatus)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("parameter = " + ctx.getRequest().getParameter().toString() + .body("parameter = " + ctx.getRequest().getParameter().toString() +
" local address = " + ctx.getRequest().getLocalAddress() + " local address = " + ctx.getRequest().getLocalAddress() +

View file

@ -51,7 +51,7 @@ public class NettyHttpServerMultiRequestLoadTest {
.setPath("/domain") .setPath("/domain")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain: " + .body("domain: " +
" base URL = " + ctx.getRequest().getBaseURL() + " base URL = " + ctx.getRequest().getBaseURL() +

View file

@ -55,7 +55,7 @@ public class NettyHttpServerRequestTest {
try { try {
String value = ctx.getRequest().getParameter().getAsString("value", Parameter.Domain.QUERY); String value = ctx.getRequest().getParameter().getAsString("value", Parameter.Domain.QUERY);
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("local address = " + ctx.getRequest().getLocalAddress() + .body("local address = " + ctx.getRequest().getLocalAddress() +
" remote address = " + ctx.getRequest().getRemoteAddress() + " remote address = " + ctx.getRequest().getRemoteAddress() +

View file

@ -52,7 +52,7 @@ public class NettyHttpServerTest {
.setPath("/domain") .setPath("/domain")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body(ctx.getRequest().asJson()) .body(ctx.getRequest().asJson())
.done(); .done();

View file

@ -35,7 +35,7 @@ public class NioHttpServerTest {
.setPath("/domain1") .setPath("/domain1")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain1 " + .body("domain1 " +
ctx.getRequest().getParameter().toString() + " " + ctx.getRequest().getParameter().toString() + " " +
@ -50,7 +50,7 @@ public class NioHttpServerTest {
.setPath("/domain2") .setPath("/domain2")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain2 " + .body("domain2 " +
ctx.getRequest().getParameter().toString() + " " + ctx.getRequest().getParameter().toString() + " " +

View file

@ -54,7 +54,7 @@ public class SimpleHttpsServerTest {
.setPath("/secure") .setPath("/secure")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("secure domain: " + .body("secure domain: " +
" SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() + " SNI host = " + ctx.getRequest().as(HttpsRequest.class).getSNIHost() +

View file

@ -41,7 +41,7 @@ public class HttpRouterTest {
.setHandler(ctx -> { .setHandler(ctx -> {
Logger.getAnonymousLogger().log(Level.INFO, "got request: " + ctx.getRequestBuilder().getRequestURI()); Logger.getAnonymousLogger().log(Level.INFO, "got request: " + ctx.getRequestBuilder().getRequestURI());
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body(ctx.getRequestBuilder().getRequestURI()); .body(ctx.getRequestBuilder().getRequestURI());
}) })

View file

@ -35,7 +35,7 @@ public class SimpleHttpServerTest {
.setPath("/domain1") .setPath("/domain1")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain1 " + .body("domain1 " +
ctx.getRequest().getParameter() + " " + ctx.getRequest().getParameter() + " " +
@ -54,7 +54,7 @@ public class SimpleHttpServerTest {
.setPath("/domain2") .setPath("/domain2")
.setHandler(ctx -> { .setHandler(ctx -> {
ctx.status(HttpResponseStatus.OK) ctx.status(HttpResponseStatus.OK)
.header(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.body("domain2 " + .body("domain2 " +
ctx.getRequest().getParameter() + " " + ctx.getRequest().getParameter() + " " +

View file

@ -155,14 +155,17 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder {
if (done) { if (done) {
return this; return this;
} }
if (name == null || value == null) {
return this;
}
if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(name.toString())) { if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(name.toString())) {
if (value.startsWith("text") && charset != null) { if (value.startsWith("text") && charset != null) {
value = value + "; charset=" + charset.name().toLowerCase(Locale.ROOT); value = value + "; charset=" + charset.name().toLowerCase(Locale.ROOT);
} }
setContentType(value); setContentType(value);
} }
if (headers.containsHeader(name)) { if (headers.containsHeader(name) && !value.equals(headers.get(name))) {
logger.log(Level.WARNING, "header already exist: " + headers.get(name) + " overwriting with " + value); logger.log(Level.WARNING, "header '" + name + "' already exist, old value = '" + headers.get(name) + "', overwriting with '" + value + "'");
} }
headers.set(name, value); headers.set(name, value);
return this; return this;
@ -173,8 +176,18 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder {
if (done) { if (done) {
return this; return this;
} }
if (name == null) {
return this;
}
if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(name.toString())) {
if (value.startsWith("text") && charset != null) {
value = value + "; charset=" + charset.name().toLowerCase(Locale.ROOT);
}
setContentType(value);
}
if (headers.containsHeader(name)) { if (headers.containsHeader(name)) {
logger.log(Level.WARNING, "header already exist: " + headers.get(name) + " adding " + value); logger.log(Level.WARNING, "when adding '" + value + "', the header already exist: " + name + " - ignoring");
return this;
} }
headers.add(name, value); headers.add(name, value);
return this; return this;
@ -315,11 +328,10 @@ public abstract class BaseHttpResponseBuilder implements HttpResponseBuilder {
@Override @Override
public BaseHttpResponseBuilder addCookie(Cookie cookie) { public BaseHttpResponseBuilder addCookie(Cookie cookie) {
if (done) { // skip done check, we force cookie add
return this; Objects.requireNonNull(cookie, "cookie must not be null when adding");
} String cookieValue = CookieEncoder.STRICT.encode(cookie);
Objects.requireNonNull(cookie); headers.add(HttpHeaderNames.SET_COOKIE,cookieValue);
headers.add(HttpHeaderNames.SET_COOKIE, CookieEncoder.STRICT.encode(cookie));
return this; return this;
} }

View file

@ -30,8 +30,8 @@ import org.xbib.net.http.server.executor.Executor;
import org.xbib.net.http.server.persist.Codec; import org.xbib.net.http.server.persist.Codec;
import org.xbib.net.http.server.render.HttpResponseRenderer; import org.xbib.net.http.server.render.HttpResponseRenderer;
import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.route.HttpRouter;
import org.xbib.net.http.server.session.IncomingSessionHandler; import org.xbib.net.http.server.session.IncomingContextHandler;
import org.xbib.net.http.server.session.OutgoingSessionHandler; import org.xbib.net.http.server.session.OutgoingContextHandler;
import org.xbib.net.http.server.session.PersistSessionHandler; import org.xbib.net.http.server.session.PersistSessionHandler;
import org.xbib.net.http.server.session.Session; import org.xbib.net.http.server.session.Session;
import org.xbib.net.http.server.session.memory.MemoryPropertiesSessionCodec; import org.xbib.net.http.server.session.memory.MemoryPropertiesSessionCodec;
@ -171,9 +171,9 @@ public class BaseApplication implements Application {
if (builder.sessionsEnabled) { if (builder.sessionsEnabled) {
Codec<Session> sessionCodec = newSessionCodec(httpRouterContext); Codec<Session> sessionCodec = newSessionCodec(httpRouterContext);
httpRouterContext.getAttributes().put("sessioncodec", sessionCodec); httpRouterContext.getAttributes().put("sessioncodec", sessionCodec);
httpRouterContext.addOpenHandler(newIncomingSessionHandler(sessionCodec)); httpRouterContext.addOpenHandler(newIncomingContextHandler(sessionCodec));
httpRouterContext.addCloseHandler(newOutgoingSessionHandler()); httpRouterContext.addCloseHandler(newOutgoingContextHandler());
httpRouterContext.addReleaseHandler(newPersistSessionHandler(sessionCodec)); httpRouterContext.addCloseHandler(newPersistHandler(sessionCodec));
} }
httpRouterContext.addCloseHandler(newOutgoingCookieHandler()); httpRouterContext.addCloseHandler(newOutgoingCookieHandler());
return httpRouterContext; return httpRouterContext;
@ -212,26 +212,22 @@ public class BaseApplication implements Application {
return new MemoryPropertiesSessionCodec(sessionName,this, 1024, Duration.ofDays(1)); return new MemoryPropertiesSessionCodec(sessionName,this, 1024, Duration.ofDays(1));
} }
protected HttpHandler newIncomingSessionHandler(Codec<Session> sessionCodec) { protected HttpHandler newIncomingContextHandler(Codec<Session> sessionCodec) {
return new IncomingSessionHandler( return new IncomingContextHandler(
getSecret(), getSecret(),
"HmacSHA1", "HmacSHA1",
sessionName, sessionName,
sessionCodec, sessionCodec,
getStaticFileSuffixes(), getStaticFileSuffixes(),
"user_id",
"e_user_id",
() -> RandomUtil.randomString(16)); () -> RandomUtil.randomString(16));
} }
protected HttpHandler newOutgoingSessionHandler() { protected HttpHandler newOutgoingContextHandler() {
return new OutgoingSessionHandler( return new OutgoingContextHandler(
getSecret(), getSecret(),
"HmacSHA1", "HmacSHA1",
sessionName, sessionName,
getStaticFileSuffixes(), getStaticFileSuffixes(),
"user_id",
"e_user_id",
Duration.ofDays(1), Duration.ofDays(1),
true, true,
false, false,
@ -239,7 +235,7 @@ public class BaseApplication implements Application {
); );
} }
protected HttpHandler newPersistSessionHandler(Codec<Session> sessionCodec) { protected HttpHandler newPersistHandler(Codec<Session> sessionCodec) {
return new PersistSessionHandler(sessionCodec); return new PersistSessionHandler(sessionCodec);
} }

View file

@ -1,6 +1,8 @@
package org.xbib.net.http.server.auth; package org.xbib.net.http.server.auth;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map;
import org.xbib.net.Attributes; import org.xbib.net.Attributes;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ -10,6 +12,10 @@ public class BaseAttributes extends LinkedHashMap<String, Object> implements Att
super(); super();
} }
public BaseAttributes(Map<String, Object> map) {
super(map);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T> T get(Class<T> cl, String key) { public <T> T get(Class<T> cl, String key) {

View file

@ -1,35 +1,33 @@
package org.xbib.net.http.server.auth; package org.xbib.net.http.server.auth;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.Collection;
import java.util.Map;
import org.xbib.datastructures.tiny.TinyMap;
import org.xbib.net.Attributes; import org.xbib.net.Attributes;
import org.xbib.net.UserProfile; import org.xbib.net.UserProfile;
public class BaseUserProfile implements UserProfile { public class BaseUserProfile implements UserProfile {
private final Attributes attributes;
private final Attributes effectiveAttributes;
private final List<String> roles;
private final List<String> effectiveRoles;
private final List<String> permissions;
private final List<String> effectivePermissions;
private String uid; private String uid;
private String euid; private String euid;
private String name; private String name;
private boolean isRemembered; private Attributes attributes;
private Collection<String> roles;
private Collection<String> effectiveRoles;
private Collection<String> permissions;
private Collection<String> effectivePermissions;
public BaseUserProfile() { public BaseUserProfile() {
this.attributes = new BaseAttributes(); this.attributes = new BaseAttributes();
this.effectiveAttributes = new BaseAttributes();
this.roles = new ArrayList<>(); this.roles = new ArrayList<>();
this.effectiveRoles = new ArrayList<>(); this.effectiveRoles = new ArrayList<>();
this.permissions = new ArrayList<>(); this.permissions = new ArrayList<>();
@ -71,105 +69,64 @@ public class BaseUserProfile implements UserProfile {
roles.add(role); roles.add(role);
} }
@Override
public void setRoles(Collection<String> roles) {
this.roles = roles;
}
@Override
public Collection<String> getRoles() {
return roles;
}
@Override @Override
public void addEffectiveRole(String role) { public void addEffectiveRole(String role) {
effectiveRoles.add(role); effectiveRoles.add(role);
} }
@Override @Override
public List<String> getRoles() { public void setEffectiveRoles(Collection<String> effectiveRoles) {
return roles; this.effectiveRoles = effectiveRoles;
} }
@Override @Override
public List<String> getEffectiveRoles() { public Collection<String> getEffectiveRoles() {
return effectiveRoles; return effectiveRoles;
} }
public boolean hasRole(String role) {
return roles.contains(role);
}
public boolean hasEffectiveRole(String role) {
return effectiveRoles.contains(role);
}
public boolean hasAccess(String requireAnyRole, String requireAllRoles) {
boolean access = true;
if (!requireAnyRole.isEmpty()) {
String[] expectedRoles = requireAnyRole.split(",");
if (!hasAnyRole(expectedRoles)) {
access = false;
}
} else if (!requireAllRoles.isEmpty()) {
String[] expectedRoles = requireAllRoles.split(",");
if (!hasAllRoles(expectedRoles)) {
access = false;
}
}
return access;
}
public boolean hasAnyRole(String[] expectedRoles) {
if (expectedRoles == null || expectedRoles.length == 0) {
return true;
}
for (final String role : expectedRoles) {
if (roles.contains(role)) {
return true;
}
}
return false;
}
public boolean hasAnyEffectiveRole(String[] expectedRoles) {
if (expectedRoles == null || expectedRoles.length == 0) {
return true;
}
for (final String role : expectedRoles) {
if (effectiveRoles.contains(role)) {
return true;
}
}
return false;
}
public boolean hasAllRoles(String[] expectedRoles) {
if (expectedRoles == null) {
return true;
}
for (String role : expectedRoles) {
if (!roles.contains(role)) {
return false;
}
}
return true;
}
public boolean hasAllEffectiveRoles(String[] expectedRoles) {
if (expectedRoles == null) {
return true;
}
for (String role : expectedRoles) {
if (!effectiveRoles.contains(role)) {
return false;
}
}
return true;
}
@Override @Override
public void addPermission(String permission) { public void addPermission(String permission) {
permissions.add(permission); permissions.add(permission);
} }
public void removePermission(String permission) { @Override
permissions.remove(permission); public void setPermissions(Collection<String> permissions) {
this.permissions = permissions;
} }
@Override @Override
public void setRemembered(boolean remembered) { public Collection<String> getPermissions() {
this.isRemembered = remembered; return permissions;
}
@Override
public void addEffectivePermission(String permission) {
effectivePermissions.add(permission);
}
@Override
public void setEffectivePermissions(Collection<String> effectivePermissions) {
this.effectivePermissions = effectivePermissions;
}
@Override
public Collection<String> getEffectivePermissions() {
return effectivePermissions;
}
@Override
public void setAttributes(Attributes attributes) {
this.attributes = attributes;
} }
@Override @Override
@ -178,38 +135,74 @@ public class BaseUserProfile implements UserProfile {
} }
@Override @Override
public Attributes getEffectiveAttributes() { public Map<String, Object> asMap() {
return effectiveAttributes; TinyMap.Builder<String, Object> builder = TinyMap.builder();
builder.put("name", getName());
String userId = getUserId();
if (userId == null) {
userId = "";
}
builder.put("user_id", userId);
String eUserId = getEffectiveUserId();
if (eUserId == null) {
eUserId = "";
}
builder.put("e_user_id", eUserId);
if (getRoles() != null && !getRoles().isEmpty()) {
builder.put("roles", getRoles());
}
if (getEffectiveRoles() != null && !getEffectiveRoles().isEmpty()) {
builder.put("e_roles", getEffectiveRoles());
}
if (getPermissions() != null && !getPermissions().isEmpty()) {
builder.put("perms", getPermissions());
}
if (getEffectivePermissions() != null && !getEffectivePermissions().isEmpty()) {
builder.put("e_perms", getEffectivePermissions());
}
if (getAttributes() != null && !getAttributes().isEmpty()) {
builder.put("attrs", getAttributes());
}
return builder.build();
} }
@Override @SuppressWarnings("unchecked")
public List<String> getPermissions() { public static UserProfile fromMap(Map<String, Object> map) {
return permissions; BaseUserProfile userProfile = new BaseUserProfile();
} if (map.containsKey("name")) {
userProfile.setName((String) map.get("name"));
@Override }
public void addEffectivePermission(String permission) { if (map.containsKey("user_id")) {
effectivePermissions.add(permission); String userId = (String) map.get("user_id");
} // empty user ID for better map transport, change it to null
@Override if (userId != null && userId.isEmpty()) {
public List<String> getEffectivePermissions() { userId = null;
return effectivePermissions; }
} userProfile.setUserId(userId);
}
@Override if (map.containsKey("e_user_id")) {
public boolean isRemembered() { String eUserId = (String) map.get("e_user_id");
return isRemembered; // empty effective user ID for better map transport, change it to null
} if (eUserId != null && eUserId.isEmpty()) {
eUserId = null;
@Override }
public String toString() { userProfile.setEffectiveUserId(eUserId);
return "uid=" + uid + }
",roles=" + roles + if (map.containsKey("roles")) {
",permissons=" + permissions + userProfile.setRoles((Collection<String>) map.get("roles"));
",attributes=" + attributes + }
",euid=" + euid + if (map.containsKey("e_roles")) {
",eroles=" + effectiveRoles + userProfile.setEffectiveRoles((Collection<String>) map.get("e_roles"));
",epermissions=" + effectivePermissions + }
",eattributes=" + effectiveAttributes; if (map.containsKey("perms")) {
userProfile.setPermissions((Collection<String>) map.get("perms"));
}
if (map.containsKey("e_perms")) {
userProfile.setEffectivePermissions((Collection<String>) map.get("e_perms"));
}
if (map.containsKey("attrs")) {
userProfile.setAttributes(new BaseAttributes((Map<String, Object>) map.get("attrs")));
}
return userProfile;
} }
} }

View file

@ -52,6 +52,6 @@ public class BasicAuthenticationHandler extends LoginAuthenticationHandler imple
} }
logger.log(Level.INFO, "unauthenticated"); logger.log(Level.INFO, "unauthenticated");
context.status(HttpResponseStatus.UNAUTHORIZED) context.status(HttpResponseStatus.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"" + getSecurityRealm().getName() + "\""); .setHeader("WWW-Authenticate", "Basic realm=\"" + getSecurityRealm().getName() + "\"");
} }
} }

View file

@ -26,7 +26,7 @@ public class FormAuthenticationHandler extends LoginAuthenticationHandler implem
logger.log(Level.FINE, "request = " + context.getRequest()); logger.log(Level.FINE, "request = " + context.getRequest());
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile"); UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
if (userProfile != null && userProfile.getUserId() != null) { if (userProfile != null && userProfile.getUserId() != null) {
logger.log(Level.FINE, "user id already set: " + userProfile.getUserId()); logger.log(Level.FINE, "already authenticated, user id = " + userProfile.getUserId());
context.setAuthenticated(true); context.setAuthenticated(true);
return; return;
} }

View file

@ -20,6 +20,7 @@ public class OutgoingCookieHandler implements HttpHandler {
CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "outgoingcookies"); CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "outgoingcookies");
if (cookieBox != null) { if (cookieBox != null) {
for (Cookie cookie : cookieBox) { for (Cookie cookie : cookieBox) {
// move cookie to http response
context.cookie(cookie); context.cookie(cookie);
logger.log(Level.FINEST, "cookie prepared for outgoing = " + cookie); logger.log(Level.FINEST, "cookie prepared for outgoing = " + cookie);
} }

View file

@ -15,7 +15,7 @@ public class BadRequestHandler implements HttpErrorHandler {
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.BAD_REQUEST) context.status(HttpResponseStatus.BAD_REQUEST)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain;charset=utf-8")
.body("Bad request") .body("Bad request")
.done(); .done();
} }

View file

@ -15,7 +15,7 @@ public class ForbiddenHandler implements HttpErrorHandler {
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.FORBIDDEN) context.status(HttpResponseStatus.FORBIDDEN)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("Forbidden") .body("Forbidden")
.done(); .done();
} }

View file

@ -32,7 +32,7 @@ public class InternalServerErrorHandler implements HttpErrorHandler {
message = throwable != null ? throwable.getMessage() : ""; message = throwable != null ? throwable.getMessage() : "";
} }
context.status(status) context.status(status)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body(message) .body(message)
.done(); .done();
} }

View file

@ -15,7 +15,7 @@ public class NotFoundHandler implements HttpErrorHandler {
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.NOT_FOUND) context.status(HttpResponseStatus.NOT_FOUND)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("Not found") .body("Not found")
.done(); .done();
} }

View file

@ -15,7 +15,7 @@ public class NotImplementedHandler implements HttpErrorHandler {
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.NOT_IMPLEMENTED) context.status(HttpResponseStatus.NOT_IMPLEMENTED)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain;charset=utf-8")
.body("Not implemented") .body("Not implemented")
.done(); .done();
} }

View file

@ -15,7 +15,7 @@ public class UnauthorizedHandler implements HttpErrorHandler {
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.UNAUTHORIZED) context.status(HttpResponseStatus.UNAUTHORIZED)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("Unauthorized") .body("Unauthorized")
.done(); .done();
} }

View file

@ -15,7 +15,7 @@ public class VersionNotSupportedHandler implements HttpErrorHandler {
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
context.status(HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED) context.status(HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED)
.header(CONTENT_TYPE, "text/plain;charset=utf-8") .setHeader(CONTENT_TYPE, "text/plain; charset=utf-8")
.body("HTTP version not supported") .body("HTTP version not supported")
.done(); .done();
} }

View file

@ -41,6 +41,7 @@ public class LdapGroupsProvider extends GroupsProvider {
* Get groups, or null if not possible. * Get groups, or null if not possible.
* @throws LdapException if unable to retrieve groups * @throws LdapException if unable to retrieve groups
*/ */
@Override
public Collection<String> getGroups(String username) { public Collection<String> getGroups(String username) {
if (userMappings == null) { if (userMappings == null) {
return null; return null;

View file

@ -46,13 +46,13 @@ public abstract class AbstractResourceHandler implements HttpHandler {
protected abstract Resource createResource(HttpRouterContext httpRouterContext) throws IOException; protected abstract Resource createResource(HttpRouterContext httpRouterContext) throws IOException;
protected abstract boolean isETagResponseEnabled(); protected abstract boolean isETagEnabled();
protected abstract boolean isCacheResponseEnabled(); protected abstract boolean isRangeEnabled();
protected abstract boolean isRangeResponseEnabled(); protected abstract int getCacheMaxAgeSeconds();
protected abstract int getMaxAgeSeconds(); protected abstract String getCacheControl();
@Override @Override
public void handle(HttpRouterContext context) throws IOException { public void handle(HttpRouterContext context) throws IOException {
@ -75,7 +75,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
.build() .build()
.toString(); .toString();
logger.log(Level.FINEST, "client must add a /, external redirect to = " + loc); logger.log(Level.FINEST, "client must add a /, external redirect to = " + loc);
context.header(HttpHeaderNames.LOCATION, loc) context.setHeader(HttpHeaderNames.LOCATION, loc)
.status(HttpResponseStatus.TEMPORARY_REDIRECT); .status(HttpResponseStatus.TEMPORARY_REDIRECT);
} else if (resource.isExistsIndexFile()) { } else if (resource.isExistsIndexFile()) {
// internal redirect to default index file in this directory // internal redirect to default index file in this directory
@ -102,7 +102,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
} }
HttpHeaders headers = context.getRequestBuilder().getHeaders(); HttpHeaders headers = context.getRequestBuilder().getHeaders();
String contentType = resource.getMimeType(); String contentType = resource.getMimeType();
context.header(CONTENT_TYPE, contentType); context.setHeader(CONTENT_TYPE, contentType);
// heuristic for inline disposition // heuristic for inline disposition
String disposition; String disposition;
if (!contentType.startsWith("text") && !contentType.startsWith("image") && !contentType.startsWith("font")) { if (!contentType.startsWith("text") && !contentType.startsWith("image") && !contentType.startsWith("font")) {
@ -114,22 +114,26 @@ public abstract class AbstractResourceHandler implements HttpHandler {
if (resource.getBaseName() != null && resource.getSuffix() != null) { if (resource.getBaseName() != null && resource.getSuffix() != null) {
String contentDisposition = disposition + ";filename=\"" + resource.getBaseName() + '.' + resource.getSuffix() + '"'; String contentDisposition = disposition + ";filename=\"" + resource.getBaseName() + '.' + resource.getSuffix() + '"';
logger.log(Level.FINEST, () -> "content type = " + contentType + " content disposition = " + contentDisposition); logger.log(Level.FINEST, () -> "content type = " + contentType + " content disposition = " + contentDisposition);
context.header(HttpHeaderNames.CONTENT_DISPOSITION, contentDisposition); context.setHeader(HttpHeaderNames.CONTENT_DISPOSITION, contentDisposition);
} }
long expirationMillis = System.currentTimeMillis() + 1000L * getMaxAgeSeconds(); if (getCacheMaxAgeSeconds() > 0) {
String expires = DateTimeUtil.formatRfc1123(expirationMillis); long expirationMillis = System.currentTimeMillis() + 1000L * getCacheMaxAgeSeconds();
if (isCacheResponseEnabled()) { String expires = DateTimeUtil.formatRfc1123(expirationMillis);
String cacheControl = "public, max-age=" + getMaxAgeSeconds(); String cacheControl = "public, max-age=" + getCacheMaxAgeSeconds();
logger.log(Level.FINEST, () -> "cache response, expires = " + expires + " cache control = " + cacheControl); logger.log(Level.FINEST, () -> "expires = " + expires + " cache control = " + cacheControl);
context.header(HttpHeaderNames.EXPIRES, expires) context.setHeader(HttpHeaderNames.EXPIRES, expires)
.header(HttpHeaderNames.CACHE_CONTROL, cacheControl); .setHeader(HttpHeaderNames.CACHE_CONTROL, cacheControl);
} else { } else {
logger.log(Level.FINEST, () -> "uncached response"); if (getCacheControl() == null) {
context.header(HttpHeaderNames.EXPIRES, "0") context.setHeader(HttpHeaderNames.EXPIRES, "0")
.header(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate"); .setHeader(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
} else {
context.setHeader(HttpHeaderNames.EXPIRES, "0")
.setHeader(HttpHeaderNames.CACHE_CONTROL, getCacheControl());
}
} }
boolean sent = false; boolean sent = false;
if (isETagResponseEnabled()) { if (isETagEnabled()) {
Instant lastModifiedInstant = resource.getLastModified(); Instant lastModifiedInstant = resource.getLastModified();
String eTag = Long.toHexString(resource.getResourcePath().hashCode() + lastModifiedInstant.toEpochMilli() + resource.getLength()); String eTag = Long.toHexString(resource.getResourcePath().hashCode() + lastModifiedInstant.toEpochMilli() + resource.getLength());
logger.log(Level.FINEST, () -> "eTag = " + eTag); logger.log(Level.FINEST, () -> "eTag = " + eTag);
@ -149,7 +153,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH); String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) { if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
logger.log(Level.FINEST, () -> "not modified, eTag = " + eTag); logger.log(Level.FINEST, () -> "not modified, eTag = " + eTag);
context.header(HttpHeaderNames.ETAG, eTag) context.setHeader(HttpHeaderNames.ETAG, eTag)
.status(HttpResponseStatus.NOT_MODIFIED); .status(HttpResponseStatus.NOT_MODIFIED);
return; return;
} }
@ -157,15 +161,15 @@ public abstract class AbstractResourceHandler implements HttpHandler {
if (ifModifiedSinceInstant != null && if (ifModifiedSinceInstant != null &&
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) { ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
logger.log(Level.FINEST, () -> "not modified (after if-modified-since), eTag = " + eTag); logger.log(Level.FINEST, () -> "not modified (after if-modified-since), eTag = " + eTag);
context.header(HttpHeaderNames.ETAG, eTag) context.setHeader(HttpHeaderNames.ETAG, eTag)
.status(HttpResponseStatus.NOT_MODIFIED); .status(HttpResponseStatus.NOT_MODIFIED);
return; return;
} }
String lastModified = DateTimeUtil.formatRfc1123(lastModifiedInstant); String lastModified = DateTimeUtil.formatRfc1123(lastModifiedInstant);
logger.log(Level.FINEST, () -> "sending resource, lastModified = " + lastModified); logger.log(Level.FINEST, () -> "sending resource, lastModified = " + lastModified);
context.header(HttpHeaderNames.ETAG, eTag) context.setHeader(HttpHeaderNames.ETAG, eTag)
.header(HttpHeaderNames.LAST_MODIFIED, lastModified); .setHeader(HttpHeaderNames.LAST_MODIFIED, lastModified);
if (isRangeResponseEnabled()) { if (isRangeEnabled()) {
performRangeResponse(context, resource, contentType, eTag, headers); performRangeResponse(context, resource, contentType, eTag, headers);
sent = true; sent = true;
} else { } else {
@ -176,7 +180,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
long length = resource.getLength(); long length = resource.getLength();
if (length > 0L) { if (length > 0L) {
String string = Long.toString(resource.getLength()); String string = Long.toString(resource.getLength());
context.header(HttpHeaderNames.CONTENT_LENGTH, string); context.setHeader(HttpHeaderNames.CONTENT_LENGTH, string);
logger.log(Level.FINEST, "length is known = " + resource.getLength()); logger.log(Level.FINEST, "length is known = " + resource.getLength());
send(resource, HttpResponseStatus.OK, contentType, context, 0L, resource.getLength()); send(resource, HttpResponseStatus.OK, contentType, context, 0L, resource.getLength());
} else { } else {
@ -194,13 +198,13 @@ public abstract class AbstractResourceHandler implements HttpHandler {
HttpHeaders headers) throws IOException { HttpHeaders headers) throws IOException {
long length = resource.getLength(); long length = resource.getLength();
logger.log(Level.FINEST, "performing range response on resource = " + resource); logger.log(Level.FINEST, "performing range response on resource = " + resource);
context.header(HttpHeaderNames.ACCEPT_RANGES, "bytes"); context.setHeader(HttpHeaderNames.ACCEPT_RANGES, "bytes");
Range full = new Range(0, length - 1, length); Range full = new Range(0, length - 1, length);
List<Range> ranges = new ArrayList<>(); List<Range> ranges = new ArrayList<>();
String range = headers.get(HttpHeaderNames.RANGE); String range = headers.get(HttpHeaderNames.RANGE);
if (range != null) { if (range != null) {
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length) context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
.status(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE); .status(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
return; return;
} }
@ -226,7 +230,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
end = length - 1; end = length - 1;
} }
if (start > end) { if (start > end) {
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length) context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
.status(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE); .status(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
return; return;
} }
@ -235,16 +239,16 @@ public abstract class AbstractResourceHandler implements HttpHandler {
} }
} }
if (ranges.isEmpty() || ranges.getFirst() == full) { if (ranges.isEmpty() || ranges.getFirst() == full) {
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total) context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total)
.header(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length)); .setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length));
send(resource, HttpResponseStatus.OK, contentType, context, full.start, full.length); send(resource, HttpResponseStatus.OK, contentType, context, full.start, full.length);
} else if (ranges.size() == 1) { } else if (ranges.size() == 1) {
Range r = ranges.getFirst(); Range r = ranges.getFirst();
context.header(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total) context.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total)
.header(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length)); .setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length));
send(resource, HttpResponseStatus.PARTIAL_CONTENT, contentType, context, r.start, r.length); send(resource, HttpResponseStatus.PARTIAL_CONTENT, contentType, context, r.start, r.length);
} else { } else {
context.header(CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY"); context.setHeader(CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Range r : ranges) { for (Range r : ranges) {
try { try {
@ -262,7 +266,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
} }
} }
context.status(HttpResponseStatus.OK) context.status(HttpResponseStatus.OK)
.header(CONTENT_TYPE, contentType) .setHeader(CONTENT_TYPE, contentType)
.body(CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1); .body(CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1);
} }
} }
@ -296,7 +300,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
} else if (context.getRequestBuilder().getMethod() == HttpMethod.HEAD) { } else if (context.getRequestBuilder().getMethod() == HttpMethod.HEAD) {
logger.log(Level.FINEST, "HEAD request, do not send body"); logger.log(Level.FINEST, "HEAD request, do not send body");
context.status(HttpResponseStatus.OK) context.status(HttpResponseStatus.OK)
.header(CONTENT_TYPE, contentType); .setHeader(CONTENT_TYPE, contentType);
} else { } else {
if ("file".equals(url.getScheme())) { if ("file".equals(url.getScheme())) {
Path path = resource.getPath(); Path path = resource.getPath();
@ -338,7 +342,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
try (ReadableByteChannel channel = fileChannel) { try (ReadableByteChannel channel = fileChannel) {
DataBuffer dataBuffer = DataBufferUtil.readBuffer(context.getDataBufferFactory(), channel, size); DataBuffer dataBuffer = DataBufferUtil.readBuffer(context.getDataBufferFactory(), channel, size);
context.status(httpResponseStatus) context.status(httpResponseStatus)
.header(CONTENT_TYPE, contentType) .setHeader(CONTENT_TYPE, contentType)
.body(dataBuffer); .body(dataBuffer);
} }
} }
@ -358,7 +362,7 @@ public abstract class AbstractResourceHandler implements HttpHandler {
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) { try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
DataBuffer dataBuffer = DataBufferUtil.readBuffer(context.getDataBufferFactory(), channel, size); DataBuffer dataBuffer = DataBufferUtil.readBuffer(context.getDataBufferFactory(), channel, size);
context.status(httpResponseStatus) context.status(httpResponseStatus)
.header(CONTENT_TYPE, contentType) .setHeader(CONTENT_TYPE, contentType)
.body(dataBuffer); .body(dataBuffer);
} }
} }

View file

@ -19,13 +19,18 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler {
private final String resourcePrefix; private final String resourcePrefix;
private final int cacheSeconds;
public ClassLoaderResourceHandler(ClassLoader classLoader) { public ClassLoaderResourceHandler(ClassLoader classLoader) {
this(classLoader, null); this(classLoader, null, 24 * 3600);
} }
public ClassLoaderResourceHandler(ClassLoader classLoader, String resourcePrefix) { public ClassLoaderResourceHandler(ClassLoader classLoader,
String resourcePrefix,
int cacheSeconds) {
this.classLoader = classLoader; this.classLoader = classLoader;
this.resourcePrefix = resourcePrefix; this.resourcePrefix = resourcePrefix;
this.cacheSeconds = cacheSeconds;
} }
@Override @Override
@ -34,23 +39,23 @@ public class ClassLoaderResourceHandler extends AbstractResourceHandler {
} }
@Override @Override
protected boolean isETagResponseEnabled() { protected boolean isETagEnabled() {
return true; return true;
} }
@Override @Override
protected boolean isCacheResponseEnabled() { protected String getCacheControl() {
return "public";
}
@Override
protected boolean isRangeEnabled() {
return true; return true;
} }
@Override @Override
protected boolean isRangeResponseEnabled() { protected int getCacheMaxAgeSeconds() {
return true; return cacheSeconds;
}
@Override
protected int getMaxAgeSeconds() {
return 24 * 3600;
} }
class ClassLoaderResource implements Resource { class ClassLoaderResource implements Resource {

View file

@ -24,14 +24,20 @@ public class FileResourceHandler extends AbstractResourceHandler {
private final String pathNameOfResource; private final String pathNameOfResource;
private final int cacheSeconds;
public FileResourceHandler() { public FileResourceHandler() {
this(null, "index.html", null); this(null, "index.html", null, 24 * 3600);
} }
public FileResourceHandler(String webRoot, String indexFileName, String pathNameOfResource) { public FileResourceHandler(String webRoot,
String indexFileName,
String pathNameOfResource,
int cacheSeconds) {
this.webRoot = webRoot; this.webRoot = webRoot;
this.indexFileName = indexFileName; this.indexFileName = indexFileName;
this.pathNameOfResource = pathNameOfResource; this.pathNameOfResource = pathNameOfResource;
this.cacheSeconds = cacheSeconds;
} }
@Override @Override
@ -58,23 +64,23 @@ public class FileResourceHandler extends AbstractResourceHandler {
} }
@Override @Override
protected boolean isETagResponseEnabled() { protected boolean isETagEnabled() {
return true; return true;
} }
@Override @Override
protected boolean isCacheResponseEnabled() { protected boolean isRangeEnabled() {
return true; return true;
} }
@Override @Override
protected boolean isRangeResponseEnabled() { protected int getCacheMaxAgeSeconds() {
return true; return cacheSeconds;
} }
@Override @Override
protected int getMaxAgeSeconds() { public String getCacheControl() {
return 24 * 3600; return "public";
} }
protected class FileResource implements Resource { protected class FileResource implements Resource {

View file

@ -27,25 +27,26 @@ public class HtmlTemplateResourceHandler extends AbstractResourceHandler {
} }
@Override @Override
protected boolean isETagResponseEnabled() { protected boolean isETagEnabled() {
return false; return false;
} }
@Override @Override
protected boolean isCacheResponseEnabled() { protected boolean isRangeEnabled() {
return false; return false;
} }
@Override @Override
protected boolean isRangeResponseEnabled() { protected int getCacheMaxAgeSeconds() {
return false; // disables caching
}
@Override
protected int getMaxAgeSeconds() {
return 0; return 0;
} }
@Override
protected String getCacheControl() {
return "no-cache";
}
public Path getRoot() { public Path getRoot() {
return root; return root;
} }

View file

@ -56,7 +56,7 @@ public class WebRootResourceResolver implements ResourceResolver {
.build() .build()
.toString(); .toString();
httpRouterContext.status(HttpResponseStatus.TEMPORARY_REDIRECT) httpRouterContext.status(HttpResponseStatus.TEMPORARY_REDIRECT)
.header("location", loc); .setHeader("location", loc);
} }
} }
return resource; return resource;

View file

@ -230,7 +230,13 @@ public class BaseHttpRouterContext implements HttpRouterContext {
} }
@Override @Override
public BaseHttpRouterContext header(String name, String value) { public BaseHttpRouterContext setHeader(String name, String value) {
httpResponseBuilder.setHeader(name, value);
return this;
}
@Override
public BaseHttpRouterContext addHeader(String name, String value) {
httpResponseBuilder.addHeader(name, value); httpResponseBuilder.addHeader(name, value);
return this; return this;
} }

View file

@ -79,7 +79,9 @@ public interface HttpRouterContext {
HttpRouterContext charset(Charset charset); HttpRouterContext charset(Charset charset);
HttpRouterContext header(String name, String value); HttpRouterContext setHeader(String name, String value);
HttpRouterContext addHeader(String name, String value);
HttpRouterContext cookie(Cookie cookie); HttpRouterContext cookie(Cookie cookie);

View file

@ -24,9 +24,9 @@ import org.xbib.net.http.server.cookie.CookieSignatureException;
import org.xbib.net.http.server.cookie.CookieSignatureUtil; import org.xbib.net.http.server.cookie.CookieSignatureUtil;
import org.xbib.net.http.server.persist.Codec; import org.xbib.net.http.server.persist.Codec;
public class IncomingSessionHandler implements HttpHandler { public class IncomingContextHandler implements HttpHandler {
private static final Logger logger = Logger.getLogger(IncomingSessionHandler.class.getName()); private static final Logger logger = Logger.getLogger(IncomingContextHandler.class.getName());
private final String sessionSecret; private final String sessionSecret;
@ -36,32 +36,21 @@ public class IncomingSessionHandler implements HttpHandler {
private final Codec<Session> sessionCodec; private final Codec<Session> sessionCodec;
private final String sessionUserName;
private final String sessionEffectiveUserName;
/**
* These suffixes disable incoming session creation.
*/
private final Set<String> suffixes; private final Set<String> suffixes;
Supplier<String> sessionIdGenerator; Supplier<String> sessionIdGenerator;
public IncomingSessionHandler(String sessionSecret, public IncomingContextHandler(String sessionSecret,
String sessionCookieAlgorithm, String sessionCookieAlgorithm,
String sessionCookieName, String sessionCookieName,
Codec<Session> sessionCodec, Codec<Session> sessionCodec,
Set<String> suffixes, Set<String> suffixes,
String sessionUserName,
String sessionEffectiveUserName,
Supplier<String> sessionIdGenerator) { Supplier<String> sessionIdGenerator) {
this.sessionSecret = sessionSecret; this.sessionSecret = sessionSecret;
this.sessionCookieAlgorithm = sessionCookieAlgorithm; this.sessionCookieAlgorithm = sessionCookieAlgorithm;
this.sessionCookieName = sessionCookieName; this.sessionCookieName = sessionCookieName;
this.sessionCodec = sessionCodec; this.sessionCodec = sessionCodec;
this.suffixes = suffixes; this.suffixes = suffixes;
this.sessionUserName = sessionUserName;
this.sessionEffectiveUserName = sessionEffectiveUserName;
this.sessionIdGenerator = sessionIdGenerator; this.sessionIdGenerator = sessionIdGenerator;
} }
@ -73,14 +62,20 @@ public class IncomingSessionHandler implements HttpHandler {
} }
Map<String, Object> payload = null; Map<String, Object> payload = null;
Session session = null; Session session = null;
UserProfile userProfile = null;
CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "incomingcookies"); CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "incomingcookies");
if (cookieBox != null) { if (cookieBox != null) {
for (Cookie cookie : cookieBox) { for (Cookie cookie : cookieBox) {
if (cookie.name().equals(sessionCookieName)) { if (cookie.name().equals(sessionCookieName)) {
logger.log(Level.FINE, "found our cookie " + sessionCookieName);
if (session == null) { if (session == null) {
try { try {
payload = decodeCookie(cookie); payload = decodeCookie(cookie);
logger.log(Level.FINE, "payload from cookie = " + payload);
session = toSession(payload); session = toSession(payload);
logger.log(Level.FINE, "session from payload = " + session);
userProfile = toUserProfile(session);
logger.log(Level.FINE, "userprofile from session = " + userProfile);
} catch (CookieSignatureException e) { } catch (CookieSignatureException e) {
// set exception in context to discard broken cookie later and render exception message // set exception in context to discard broken cookie later and render exception message
context.getAttributes().put("_throwable", e); context.getAttributes().put("_throwable", e);
@ -97,19 +92,63 @@ public class IncomingSessionHandler implements HttpHandler {
if (session == null) { if (session == null) {
try { try {
session = sessionCodec.create(sessionIdGenerator.get()); session = sessionCodec.create(sessionIdGenerator.get());
logger.log(Level.FINE, "creating new session " + session.id());
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);
throw new HttpException("unable to create session", context, HttpResponseStatus.INTERNAL_SERVER_ERROR); throw new HttpException("unable to create new session", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
} }
} }
if (payload != null) { if (userProfile == null) {
UserProfile userProfile = newUserProfile(payload, session); logger.log(Level.FINE, "creating new user profile");
if (userProfile != null) { userProfile = newUserProfile(payload);
context.getAttributes().put("userprofile", userProfile);
}
} }
logger.log(Level.FINEST, "incoming session ID = " + session.id() + " keys = " + session.keySet());
context.getAttributes().put("session", session); context.getAttributes().put("session", session);
context.getAttributes().put("userprofile", userProfile);
}
protected Session toSession(Map<String, Object> map) {
return toSession((String) map.get("id"), map);
}
protected Session toSession(String id, Map<String, Object> map) {
Session session = null;
try {
session = sessionCodec.read(id);
if (session != null && map != null) {
logger.log(Level.FINE, "session id " + id + " restored");
session.putAll(map);
}
} catch (Exception e) {
logger.log(Level.FINEST, "unable to restore session, id = " + id, e);
}
return session;
}
@SuppressWarnings("unchecked")
protected UserProfile toUserProfile(Session session) {
Map<String, Object> map = (Map<String, Object>) session.get("userprofile");
return map != null ? BaseUserProfile.fromMap(map) : null;
}
@SuppressWarnings("unchecked")
protected UserProfile newUserProfile(Map<String, Object> cookieMap) {
UserProfile userProfile = new BaseUserProfile();
// user_id, e_user_id are in cookie map
if (cookieMap != null) {
Map<String, Object> m = (Map<String, Object>) cookieMap.get("map");
if (m == null) {
// nothing found
return userProfile;
}
if (m.containsKey("user_id")) {
userProfile.setUserId((String) m.get("user_id"));
}
if (m.containsKey("e_user_id")) {
userProfile.setEffectiveUserId((String) m.get("e_user_id"));
}
// roles, permissions, attributes must be filled later
}
return userProfile;
} }
private Map<String, Object> decodeCookie(Cookie cookie) throws IOException, private Map<String, Object> decodeCookie(Cookie cookie) throws IOException,
@ -127,48 +166,10 @@ public class IncomingSessionHandler implements HttpHandler {
String sig = s[2]; String sig = s[2];
String mysig = CookieSignatureUtil.hmac(payload, sessionSecret, sessionCookieAlgorithm); String mysig = CookieSignatureUtil.hmac(payload, sessionSecret, sessionCookieAlgorithm);
if (!sig.equals(mysig)) { if (!sig.equals(mysig)) {
logger.log(Level.SEVERE, MessageFormat.format("signature in cookie does not match. algo={1} secret={2} payload={3} sig={4} mysig={5}", logger.log(Level.SEVERE, () -> MessageFormat.format("signature in cookie does not match. algo={0} secret={1} payload={2} sig={3} mysig={4}",
sessionCookieAlgorithm, sessionSecret, payload, sig, mysig)); sessionCookieAlgorithm, sessionSecret, payload, sig, mysig));
throw new CookieSignatureException("cookie security problem"); throw new CookieSignatureException("cookie security problem");
} }
Map<String, Object> map = CookieSignatureUtil.toMap(payload); return Map.of("id", id, "payload", payload, "map", CookieSignatureUtil.toMap(payload));
return Map.of("id", id, "payload", payload, "map", map);
}
protected Session toSession(Map<String, Object> map) {
return toSession((String) map.get("id"), map);
}
protected Session toSession(String id, Map<String, Object> map) {
Session session = null;
try {
session = sessionCodec.read(id);
if (session != null && map != null) {
session.putAll(map);
}
} catch (Exception e) {
logger.log(Level.FINEST, "unable to read session, id = " + id, e);
}
return session;
}
@SuppressWarnings("unchecked")
protected UserProfile newUserProfile(Map<String, Object> map, Session session) {
UserProfile userProfile = new BaseUserProfile();
Map<String, Object> m = (Map<String, Object>) map.get("map");
if (m == null) {
return userProfile;
}
if (sessionUserName != null && m.containsKey(sessionUserName)) {
userProfile.setUserId((String) m.get(sessionUserName));
}
if (sessionEffectiveUserName != null && m.containsKey(sessionEffectiveUserName)) {
userProfile.setEffectiveUserId((String) m.get(sessionEffectiveUserName));
}
if (session != null && userProfile.getUserId() != null) {
session.put("user_id", userProfile.getUserId());
session.put("e_user_id", userProfile.getUserId());
}
return userProfile;
} }
} }

View file

@ -5,7 +5,6 @@ import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
@ -25,9 +24,9 @@ import org.xbib.net.http.server.application.Application;
import org.xbib.net.http.server.cookie.CookieSignatureException; import org.xbib.net.http.server.cookie.CookieSignatureException;
import org.xbib.net.http.server.cookie.CookieSignatureUtil; import org.xbib.net.http.server.cookie.CookieSignatureUtil;
public class OutgoingSessionHandler implements HttpHandler { public class OutgoingContextHandler implements HttpHandler {
private static final Logger logger = Logger.getLogger(OutgoingSessionHandler.class.getName()); private static final Logger logger = Logger.getLogger(OutgoingContextHandler.class.getName());
private final String sessionSecret; private final String sessionSecret;
@ -39,22 +38,16 @@ public class OutgoingSessionHandler implements HttpHandler {
private final Set<String> suffixes; private final Set<String> suffixes;
private final String sessionUserName;
private final String sessionEffectiveUserName;
private final boolean httpOnly; private final boolean httpOnly;
private final boolean secure; private final boolean secure;
private final SameSite sameSite; private final SameSite sameSite;
public OutgoingSessionHandler(String sessionSecret, public OutgoingContextHandler(String sessionSecret,
String sessionCookieAlgorithm, String sessionCookieAlgorithm,
String sessionCookieName, String sessionCookieName,
Set<String> suffixes, Set<String> suffixes,
String sessionUserName,
String sessionEffectiveUserName,
Duration sessionDuration, Duration sessionDuration,
boolean httpOnly, boolean httpOnly,
boolean secure, boolean secure,
@ -63,8 +56,6 @@ public class OutgoingSessionHandler implements HttpHandler {
this.sessionCookieAlgorithm = sessionCookieAlgorithm; this.sessionCookieAlgorithm = sessionCookieAlgorithm;
this.sessionCookieName = sessionCookieName; this.sessionCookieName = sessionCookieName;
this.suffixes = suffixes; this.suffixes = suffixes;
this.sessionUserName = sessionUserName;
this.sessionEffectiveUserName = sessionEffectiveUserName;
this.sessionDuration = sessionDuration; this.sessionDuration = sessionDuration;
this.httpOnly = httpOnly; this.httpOnly = httpOnly;
this.secure = secure; this.secure = secure;
@ -79,7 +70,6 @@ public class OutgoingSessionHandler implements HttpHandler {
} }
String suffix = SessionUtil.extractExtension(context.getRequestBuilder().getRequestPath()); String suffix = SessionUtil.extractExtension(context.getRequestBuilder().getRequestPath());
if (suffix != null && suffixes.contains(suffix)) { if (suffix != null && suffixes.contains(suffix)) {
logger.log(Level.FINEST, () -> "suffix " + suffix + " blocking outgoing session handling");
return; return;
} }
CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "outgoingcookies"); CookieBox cookieBox = context.getAttributes().get(CookieBox.class, "outgoingcookies");
@ -87,7 +77,6 @@ public class OutgoingSessionHandler implements HttpHandler {
cookieBox = new CookieBox(); cookieBox = new CookieBox();
} }
Application application = context.getAttributes().get(Application.class, "application"); Application application = context.getAttributes().get(Application.class, "application");
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
String host = context.getContextURL().getHost(); String host = context.getContextURL().getHost();
String path = application.getContextPath(); String path = application.getContextPath();
Throwable throwable = context.getAttributes().get(Throwable.class, "_throwable"); Throwable throwable = context.getAttributes().get(Throwable.class, "_throwable");
@ -99,16 +88,11 @@ public class OutgoingSessionHandler implements HttpHandler {
Session session = context.getAttributes().get(Session.class, "session"); Session session = context.getAttributes().get(Session.class, "session");
if (session != null) { if (session != null) {
try { try {
UserProfile userProfile = context.getAttributes().get(UserProfile.class, "userprofile");
if (userProfile != null) { if (userProfile != null) {
logger.log(Level.FINEST, () -> "user profile present: " + userProfile); session.put("userprofile", userProfile.asMap());
if (sessionUserName != null) {
session.put(sessionUserName, userProfile.getUserId());
}
if (sessionEffectiveUserName != null) {
session.put(sessionEffectiveUserName, userProfile.getEffectiveUserId());
}
} }
Cookie cookie = encodeCookie(session, host, path); Cookie cookie = encodeCookie(session, userProfile, host, path);
if (cookie != null) { if (cookie != null) {
cookieBox.add(cookie); cookieBox.add(cookie);
} }
@ -117,12 +101,18 @@ public class OutgoingSessionHandler implements HttpHandler {
throw new HttpException("unable to create session data for cookie", context, HttpResponseStatus.INTERNAL_SERVER_ERROR); throw new HttpException("unable to create session data for cookie", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
} }
} }
logger.log(Level.FINEST, "prepared outgoing cookies = " + cookieBox);
context.getAttributes().put("outgoingcookies", cookieBox); context.getAttributes().put("outgoingcookies", cookieBox);
} }
private Cookie encodeCookie(Session session, String host, String path) throws IOException, private Cookie encodeCookie(Session session,
UserProfile userProfile,
String host,
String path) throws IOException,
NoSuchAlgorithmException, InvalidKeyException { NoSuchAlgorithmException, InvalidKeyException {
if (session == null) {
logger.log(Level.WARNING, "no session, no cookie");
return null;
}
if (sessionSecret == null) { if (sessionSecret == null) {
logger.log(Level.WARNING, "no secret, no cookie"); logger.log(Level.WARNING, "no secret, no cookie");
return null; return null;
@ -137,13 +127,10 @@ public class OutgoingSessionHandler implements HttpHandler {
session.invalidate(); session.invalidate();
return createEmptyCookie(host, path); return createEmptyCookie(host, path);
} }
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = userProfile != null ?
if (sessionUserName != null) { Map.of("user_id", userProfile.getUserId() != null ? userProfile.getUserId() :"",
map.put(sessionUserName, session.get(sessionUserName)); "e_user_id", userProfile.getEffectiveUserId() != null ? userProfile.getEffectiveUserId() : "") :
} Map.of();
if (sessionEffectiveUserName != null) {
map.put(sessionEffectiveUserName, session.get(sessionEffectiveUserName));
}
String payload = CookieSignatureUtil.toString(map); String payload = CookieSignatureUtil.toString(map);
String sig = CookieSignatureUtil.hmac(payload, sessionSecret, sessionCookieAlgorithm); String sig = CookieSignatureUtil.hmac(payload, sessionSecret, sessionCookieAlgorithm);
String cookieValue = String.join(":", id, payload, sig); String cookieValue = String.join(":", id, payload, sig);

View file

@ -28,7 +28,7 @@ public class PersistSessionHandler implements HttpHandler {
sessionCodec.write(session.id(), session); sessionCodec.write(session.id(), session);
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);
throw new HttpException("unable to create session data for cookie", context, HttpResponseStatus.INTERNAL_SERVER_ERROR); throw new HttpException("unable to write session data", context, HttpResponseStatus.INTERNAL_SERVER_ERROR);
} }
} }
} }

View file

@ -30,9 +30,9 @@ public class GroovyTemplateRenderer implements HttpHandler {
} }
HttpResponseStatus httpResponseStatus = context.getAttributes().get(HttpResponseStatus.class, "_status", HttpResponseStatus.OK); HttpResponseStatus httpResponseStatus = context.getAttributes().get(HttpResponseStatus.class, "_status", HttpResponseStatus.OK);
context.status(httpResponseStatus) context.status(httpResponseStatus)
.header("cache-control", "no-cache") // override default must-revalidate behavior .setHeader("cache-control", "no-cache")
.header("content-length", Integer.toString(dataBuffer.writePosition())) .setHeader("content-length", Integer.toString(dataBuffer.writePosition()))
.header(CONTENT_TYPE, "text/html; charset=" + StandardCharsets.UTF_8.displayName()) .setHeader(CONTENT_TYPE, "text/html; charset=" + StandardCharsets.UTF_8.displayName())
.body(dataBuffer); .body(dataBuffer);
} }
} }