From 816266e2b29461af826fe459e070780a82cdb70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Tue, 6 Aug 2019 16:33:18 +0200 Subject: [PATCH] clean up util classes, add cookie signing helper class --- config/checkstyle/checkstyle.xml | 323 ------------------ gradle.properties | 9 +- gradle/wrapper/gradle-wrapper.properties | 6 +- .../org/xbib/netty/http/client/Client.java | 2 +- .../xbib/netty/http/client/ClientConfig.java | 2 +- .../client/cookie/ClientCookieDecoder.java | 18 +- .../test/cookie/ClientCookieDecoderTest.java | 27 +- .../test/cookie/ClientCookieEncoderTest.java | 5 +- .../xbib/netty/http/common/cookie/Cookie.java | 2 - .../http/common/cookie/CookieSigner.java | 169 +++++++++ .../http/common/cookie/DefaultCookie.java | 8 +- .../netty/http/common/cookie/Payload.java | 78 ----- .../netty/http/common/cookie/SameSite.java | 5 + .../http/common/{util => security}/Algo.java | 2 +- .../netty/http/common/security/Codec.java | 5 + .../CryptUtil.java} | 38 ++- .../http/common/{util => security}/HMac.java | 6 +- .../common/{ => security}/SecurityUtil.java | 5 +- .../xbib/netty/http/common/util/Codec.java | 5 - .../{DateTimeUtils.java => DateTimeUtil.java} | 5 +- .../common/test/cookie/CookieSignerTest.java | 85 +++++ .../common/test/cookie/SignedCookieTest.java | 72 ---- .../CryptUtilTest.java} | 20 +- .../org/xbib/netty/http/server/Domain.java | 4 +- .../org/xbib/netty/http/server/Server.java | 6 +- .../server/cookie/ServerCookieEncoder.java | 48 +-- .../endpoint/HttpEndpointDescriptor.java | 2 +- .../server/endpoint/HttpEndpointResolver.java | 27 +- .../endpoint/service/ResourceService.java | 16 +- .../test/cookie/ServerCookieEncoderTest.java | 4 +- 30 files changed, 396 insertions(+), 608 deletions(-) delete mode 100644 config/checkstyle/checkstyle.xml create mode 100644 netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieSigner.java delete mode 100644 netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Payload.java create mode 100644 netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/SameSite.java rename netty-http-common/src/main/java/org/xbib/netty/http/common/{util => security}/Algo.java (88%) create mode 100644 netty-http-common/src/main/java/org/xbib/netty/http/common/security/Codec.java rename netty-http-common/src/main/java/org/xbib/netty/http/common/{util/CryptUtils.java => security/CryptUtil.java} (75%) rename netty-http-common/src/main/java/org/xbib/netty/http/common/{util => security}/HMac.java (60%) rename netty-http-common/src/main/java/org/xbib/netty/http/common/{ => security}/SecurityUtil.java (94%) delete mode 100644 netty-http-common/src/main/java/org/xbib/netty/http/common/util/Codec.java rename netty-http-common/src/main/java/org/xbib/netty/http/common/util/{DateTimeUtils.java => DateTimeUtil.java} (97%) create mode 100644 netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/CookieSignerTest.java delete mode 100644 netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/SignedCookieTest.java rename netty-http-common/src/test/java/org/xbib/netty/http/common/test/{CryptUtilsTest.java => security/CryptUtilTest.java} (60%) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index 3dc3e16..0000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle.properties b/gradle.properties index cc9dda8..6e7bda7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http -version = 4.1.36.7 +version = 4.1.36.8 # main packages netty.version = 4.1.36.Final @@ -18,14 +18,13 @@ reactivestreams.version = 1.0.2 # rest xbib-guice.version = 4.0.4 -# xmlrpc-client -commons-httpclient.version = 3.1 - # test packages junit.version = 5.4.2 junit4.version = 4.12 conscrypt.version = 2.0.0 -jackson.version = 2.8.11.1 +jackson.version = 2.9.9 + +# doc asciidoclet.version = 1.5.4 org.gradle.warning.mode = all diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c157b6f..128e8c2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Apr 22 17:45:04 CEST 2019 +#Tue Aug 06 15:30:36 CEST 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java index abd2699..9bfe22c 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java @@ -34,7 +34,7 @@ import org.xbib.netty.http.client.transport.HttpTransport; import org.xbib.netty.http.client.transport.Transport; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.NetworkUtils; -import org.xbib.netty.http.common.SecurityUtil; +import org.xbib.netty.http.common.security.SecurityUtil; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIServerName; diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java index 4be2491..c3f5c10 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/ClientConfig.java @@ -10,7 +10,7 @@ import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.SslProvider; import org.xbib.netty.http.client.retry.BackOff; import org.xbib.netty.http.common.HttpAddress; -import org.xbib.netty.http.common.SecurityUtil; +import org.xbib.netty.http.common.security.SecurityUtil; import javax.net.ssl.TrustManagerFactory; import java.io.InputStream; diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/cookie/ClientCookieDecoder.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/cookie/ClientCookieDecoder.java index 2fc7ebc..e8882b6 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/cookie/ClientCookieDecoder.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/cookie/ClientCookieDecoder.java @@ -4,7 +4,8 @@ import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.CookieDecoder; import org.xbib.netty.http.common.cookie.CookieHeaderNames; import org.xbib.netty.http.common.cookie.DefaultCookie; -import org.xbib.netty.http.common.util.DateTimeUtils; +import org.xbib.netty.http.common.cookie.SameSite; +import org.xbib.netty.http.common.util.DateTimeUtil; import java.time.Instant; import java.util.Locale; @@ -42,7 +43,7 @@ public final class ClientCookieDecoder extends CookieDecoder { public Cookie decode(String header) { final int headerLen = Objects.requireNonNull(header, "header").length(); if (headerLen == 0) { - return null; + throw new IllegalArgumentException("header length is 0"); } CookieBuilder cookieBuilder = null; int i = 0; @@ -116,7 +117,10 @@ public final class ClientCookieDecoder extends CookieDecoder { } } } - return cookieBuilder != null ? cookieBuilder.cookie() : null; + if (cookieBuilder == null) { + throw new IllegalArgumentException("no cookie found"); + } + return cookieBuilder.cookie(); } private static class CookieBuilder { @@ -139,7 +143,7 @@ public final class ClientCookieDecoder extends CookieDecoder { private boolean httpOnly; - private Cookie.SameSite sameSite = Cookie.SameSite.STRICT; + private SameSite sameSite = SameSite.STRICT; CookieBuilder(DefaultCookie cookie, String header) { this.cookie = cookie; @@ -208,7 +212,7 @@ public final class ClientCookieDecoder extends CookieDecoder { if (maxAge != Long.MIN_VALUE) { return maxAge; } else if (isValueDefined(expiresStart, expiresEnd)) { - Instant expiresDate = DateTimeUtils.parseDate(header, expiresStart, expiresEnd); + Instant expiresDate = DateTimeUtil.parseDate(header, expiresStart, expiresEnd); if (expiresDate != null) { Instant now = Instant.now(); long maxAgeMillis = expiresDate.toEpochMilli() - now.toEpochMilli(); @@ -233,7 +237,7 @@ public final class ClientCookieDecoder extends CookieDecoder { } else if (header.regionMatches(true, nameStart, CookieHeaderNames.SAMESITE, 0, 8)) { String string = computeValue(valueStart, valueEnd); if (string != null) { - setSameSite(Cookie.SameSite.valueOf(string.toUpperCase(Locale.ROOT))); + setSameSite(SameSite.valueOf(string.toUpperCase(Locale.ROOT))); } } } @@ -246,7 +250,7 @@ public final class ClientCookieDecoder extends CookieDecoder { return isValueDefined(valueStart, valueEnd) ? header.substring(valueStart, valueEnd) : null; } - private void setSameSite(Cookie.SameSite value) { + private void setSameSite(SameSite value) { sameSite = value; } } diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/cookie/ClientCookieDecoderTest.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/cookie/ClientCookieDecoderTest.java index 2c359ab..9ea09e3 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/cookie/ClientCookieDecoderTest.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/cookie/ClientCookieDecoderTest.java @@ -3,7 +3,8 @@ package org.xbib.netty.http.client.test.cookie; import org.junit.jupiter.api.Test; import org.xbib.netty.http.client.cookie.ClientCookieDecoder; import org.xbib.netty.http.common.cookie.Cookie; -import org.xbib.netty.http.common.util.DateTimeUtils; +import org.xbib.netty.http.common.cookie.SameSite; +import org.xbib.netty.http.common.util.DateTimeUtil; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -24,7 +25,7 @@ class ClientCookieDecoderTest { void testDecodingSingleCookieV0() { long millis = System.currentTimeMillis() + 50000; String cookieString = "myCookie=myValue;expires=" + - DateTimeUtils.formatMillis(millis) + + DateTimeUtil.formatMillis(millis) + ";path=/apathsomewhere;domain=.adomainsomewhere;secure;"; Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString); assertNotNull(cookie); @@ -55,8 +56,8 @@ class ClientCookieDecoderTest { String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;domain=.adomainsomewhere" + ";secure;comment=this is a comment;version=1;"; Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString); - assertEquals("myValue", cookie.value()); assertNotNull(cookie); + assertEquals("myValue", cookie.value()); assertEquals(".adomainsomewhere", cookie.domain()); assertEquals(50, cookie.maxAge()); assertEquals("/apathsomewhere", cookie.path()); @@ -134,6 +135,7 @@ class ClientCookieDecoderTest { "__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" + "utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html"; Cookie cookie = ClientCookieDecoder.STRICT.decode(source); + assertNotNull(cookie); assertEquals("ARPT", cookie.name()); assertEquals("LWUKQPSWRTUN04CKKJI", cookie.value()); } @@ -144,6 +146,7 @@ class ClientCookieDecoderTest { long expectedMaxAge = ((zonedDateTime.toEpochSecond() * 1000L) - System.currentTimeMillis()) / 1000; String source = "Format=EU; expires=Fri, 31-Dec-2100 23:59:59 GMT; path=/"; Cookie cookie = ClientCookieDecoder.STRICT.decode(source); + assertNotNull(cookie); assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2); } @@ -159,6 +162,7 @@ class ClientCookieDecoderTest { void testDecodingWeirdNames1() { String src = "path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com"; Cookie cookie = ClientCookieDecoder.STRICT.decode(src); + assertNotNull(cookie); assertEquals("path", cookie.name()); assertEquals("", cookie.value()); assertEquals("/", cookie.path()); @@ -168,15 +172,14 @@ class ClientCookieDecoderTest { void testDecodingWeirdNames2() { String src = "HTTPOnly="; Cookie cookie = ClientCookieDecoder.STRICT.decode(src); + assertNotNull(cookie); assertEquals("HTTPOnly", cookie.name()); assertEquals("", cookie.value()); } @Test void testDecodingValuesWithCommasAndEqualsFails() { - String src = "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E"; - Cookie cookie = ClientCookieDecoder.STRICT.decode(src); - assertNull(cookie); + assertNull(ClientCookieDecoder.STRICT.decode( "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E")); } @Test @@ -226,6 +229,7 @@ class ClientCookieDecoderTest { "%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V { - ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar")); - }); + assertThrows(IllegalArgumentException.class, () -> + ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar"))); } } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Cookie.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Cookie.java index fb41eae..4890b0f 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Cookie.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Cookie.java @@ -147,6 +147,4 @@ public interface Cookie extends Comparable { * @param sameSite the same site value */ void setSameSite(SameSite sameSite); - - enum SameSite { STRICT, LAX } } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieSigner.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieSigner.java new file mode 100644 index 0000000..1ca2f1f --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieSigner.java @@ -0,0 +1,169 @@ +package org.xbib.netty.http.common.cookie; + +import org.xbib.net.PercentDecoder; +import org.xbib.net.PercentEncoder; +import org.xbib.net.PercentEncoders; +import org.xbib.netty.http.common.security.Codec; +import org.xbib.netty.http.common.security.CryptUtil; +import org.xbib.netty.http.common.security.HMac; + +import java.nio.charset.Charset; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.MalformedInputException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnmappableCharacterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public class CookieSigner { + + private String signature; + + private final String publicValue; + + private final String privateValue; + + private String cookieValue; + + /** + * Construct cookie for signing. + * + * @param charset the character set + * @param hmac the HMAC code + * @param codec the codec for the private value + * @param privateValue the private value + * @param publicValue the public value + * @param secret the secret + * @throws MalformedInputException if signing cookie fails + * @throws UnmappableCharacterException if signing cookie fails + * @throws NoSuchAlgorithmException if signing cookie fails + * @throws InvalidKeyException if signing cookie fails + */ + private CookieSigner(Charset charset, HMac hmac, Codec codec, String privateValue, String publicValue, String secret) + throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException { + PercentEncoder percentEncoder = PercentEncoders.getCookieEncoder(charset); + this.privateValue = privateValue; + this.publicValue = publicValue; + this.signature = CryptUtil.hmac(charset, hmac, codec, privateValue, secret); + this.cookieValue = percentEncoder.encode(String.join(":", publicValue, privateValue, signature)); + } + + /** + * Parse signed cookie value. + * + * @param charset the character set + * @param hmac the HMAC code + * @param codec the codec for the private value + * @param rawValue the raw value for parsing + * @param secret the secret + * @throws MalformedInputException if parsing failed + * @throws UnmappableCharacterException if parsing failed + * @throws NoSuchAlgorithmException if parsing failed + * @throws InvalidKeyException if parsing failed + * @throws SignatureException if signature is invalid + */ + private CookieSigner(Charset charset, HMac hmac, Codec codec, String rawValue, String secret) + throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException, + SignatureException { + PercentDecoder persentDecoder = new PercentDecoder(charset.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + ); + String[] s = persentDecoder.decode(rawValue).split(":", 3); + if (s.length != 3) { + throw new IllegalStateException("unable to find three colon-separated components in cookie value"); + } + this.signature = CryptUtil.hmac(charset, hmac, codec, s[1], secret); + if (!s[2].equals(signature)) { + throw new SignatureException("HMAC signature does not match"); + } + this.publicValue = s[0]; + this.privateValue = s[1]; + this.cookieValue = rawValue; + } + + public String getPublicValue() { + return publicValue; + } + + public String getPrivateValue() { + return privateValue; + } + + public String getCookieValue() { + return cookieValue; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Charset charset; + + private HMac hmac; + + private Codec codec; + + private String privateValue; + + private String publicValue; + + private String secret; + + private String rawValue; + + public Builder() { + this.charset = StandardCharsets.UTF_8; + this.hmac = HMac.HMAC_SHA1; + this.codec = Codec.BASE64; + } + + public Builder withCharset(Charset charset) { + this.charset = charset; + return this; + } + + public Builder withHMac(HMac hmac) { + this.hmac = hmac; + return this; + } + + public Builder withCodec(Codec codec) { + this.codec = codec; + return this; + } + + public Builder withPrivateValue(String privateValue) { + this.privateValue = privateValue; + return this; + } + + public Builder withPublicValue(String publicValue) { + this.publicValue = publicValue; + return this; + } + + public Builder withSecret(String secret) { + this.secret = secret; + return this; + } + + public Builder withRawValue(String rawValue) { + this.rawValue = rawValue; + return this; + } + + public CookieSigner build() { + try { + return rawValue != null ? + new CookieSigner(charset, hmac, codec, rawValue, secret) : + new CookieSigner(charset, hmac, codec, privateValue, publicValue, secret); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + } +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/DefaultCookie.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/DefaultCookie.java index e2277cd..ff4752d 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/DefaultCookie.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/DefaultCookie.java @@ -25,6 +25,10 @@ public class DefaultCookie implements Cookie { private SameSite sameSite; + public DefaultCookie(String name, CookieSigner cookieSigner) { + this(name, cookieSigner.getCookieValue()); + } + /** * Creates a new cookie with the specified name and value. * @param name name @@ -38,10 +42,6 @@ public class DefaultCookie implements Cookie { setValue(value); } - public DefaultCookie(String name, Payload payload) { - this(name, payload.toString()); - } - @Override public String name() { return name; diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Payload.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Payload.java deleted file mode 100644 index c296b92..0000000 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/Payload.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.xbib.netty.http.common.cookie; - -import org.xbib.net.PercentDecoder; -import org.xbib.net.PercentEncoder; -import org.xbib.net.PercentEncoders; -import org.xbib.netty.http.common.util.Codec; -import org.xbib.netty.http.common.util.CryptUtils; -import org.xbib.netty.http.common.util.HMac; - -import java.nio.charset.MalformedInputException; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnmappableCharacterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; - -public class Payload { - - private static final PercentEncoder PERCENT_ENCODER = PercentEncoders.getCookieEncoder(StandardCharsets.UTF_8); - - private static final PercentDecoder PERCENT_DECODER = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()); - - private final Codec codec; - - private final HMac hmac; - - private final String publicValue; - - private final String privateValue; - - private final String secret; - - public Payload(Codec codec, HMac hmac, String publicValue, String privateValue, String secret) { - this.codec = codec; - this.hmac = hmac; - this.publicValue = publicValue; - this.privateValue = privateValue; - this.secret = secret; - } - - public Payload(Codec codec, HMac hmac, String rawValue, String secret) - throws MalformedInputException, UnmappableCharacterException, InvalidKeyException, - NoSuchAlgorithmException, SignatureException { - this.codec = codec; - this.hmac = hmac; - String[] s = PERCENT_DECODER.decode(rawValue).split(":", 3); - if (s.length != 3) { - throw new IllegalStateException(); - } - this.publicValue = s[0]; - this.privateValue = s[1]; - this.secret = secret; - if (!s[2].equals(CryptUtils.hmac(codec, privateValue, secret, hmac))) { - throw new SignatureException("HMAC signature does not match"); - } - } - - public String getPublicValue() { - return publicValue; - } - - public String getPrivateValue() { - return privateValue; - } - - public String getSecret() { - return secret; - } - - public String toString() { - try { - return PERCENT_ENCODER.encode(String.join(":", publicValue, privateValue, - CryptUtils.hmac(codec, privateValue, secret, hmac))); - } catch (NoSuchAlgorithmException | InvalidKeyException | MalformedInputException | UnmappableCharacterException e) { - throw new IllegalArgumentException(e); - } - } -} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/SameSite.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/SameSite.java new file mode 100644 index 0000000..26fdae3 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/SameSite.java @@ -0,0 +1,5 @@ +package org.xbib.netty.http.common.cookie; + +public enum SameSite { + STRICT, LAX +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/Algo.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/Algo.java similarity index 88% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/util/Algo.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/security/Algo.java index 4dc2fae..3f03e48 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/Algo.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/Algo.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.common.util; +package org.xbib.netty.http.common.security; public enum Algo { MD5("MD5", "md5"), diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/security/Codec.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/Codec.java new file mode 100644 index 0000000..2bbab5b --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/Codec.java @@ -0,0 +1,5 @@ +package org.xbib.netty.http.common.security; + +public enum Codec { + BASE64, HEX +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/CryptUtils.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/CryptUtil.java similarity index 75% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/util/CryptUtils.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/security/CryptUtil.java index 4adac32..f0a86f4 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/CryptUtils.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/CryptUtil.java @@ -1,7 +1,8 @@ -package org.xbib.netty.http.common.util; +package org.xbib.netty.http.common.security; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; @@ -15,10 +16,13 @@ import java.util.Base64; * A utility class for invoking encryption methods and returning password strings, * using {@link java.security.MessageDigest} and {@link javax.crypto.Mac}. */ -public class CryptUtils { +public class CryptUtil { private static final Random random = new SecureRandom(); + private CryptUtil() { + } + public static String randomHex(int length) { byte[] b = new byte[length]; random.nextBytes(b); @@ -53,32 +57,32 @@ public class CryptUtils { return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), salt, Algo.SSHA512.algo, Algo.SSHA512.prefix); } - public static String hmacSHA1(String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException { - return hmac(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA1); + public static String hmacSHA1(Charset charset, String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException { + return hmac(HMac.HMAC_SHA1, Codec.BASE64, plainText.getBytes(charset), secret.getBytes(charset)); } - public static String hmacSHA1(byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException { - return hmac(Codec.BASE64, plainText, secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA1); + public static String hmacSHA1(Charset charset, byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException { + return hmac(HMac.HMAC_SHA1, Codec.BASE64, plainText, secret.getBytes(charset)); } public static String hmacSHA1(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException { - return hmac(Codec.BASE64, plainText, secret, HMac.HMAC_SHA1); + return hmac(HMac.HMAC_SHA1, Codec.BASE64, plainText, secret); } - public static String hmacSHA256(String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException { - return hmac(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA256); + public static String hmacSHA256(Charset charset, String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException { + return hmac(HMac.HMAC_SHA256, Codec.BASE64, plainText.getBytes(charset), secret.getBytes(charset)); } - public static String hmacSHA256(byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException { - return hmac(Codec.BASE64, plainText, secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA256); + public static String hmacSHA256(Charset charset, byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException { + return hmac(HMac.HMAC_SHA256, Codec.BASE64, plainText, secret.getBytes(charset)); } public static String hmacSHA256(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException { - return hmac(Codec.BASE64, plainText, secret, HMac.HMAC_SHA256); + return hmac(HMac.HMAC_SHA256, Codec.BASE64, plainText, secret); } - public static String hmac(Codec codec, String plainText, String secret, HMac hmac) throws InvalidKeyException, NoSuchAlgorithmException { - return hmac(codec, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), hmac); + public static String hmac(Charset charset, HMac hmac, Codec codec, String plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException { + return hmac(hmac, codec, plainText.getBytes(charset), secret.getBytes(charset)); } public static String digest(Codec codec, byte[] plainText, byte[] salt, String algo, String prefix) throws NoSuchAlgorithmException { @@ -98,11 +102,11 @@ public class CryptUtils { codec == Codec.HEX ? encodeHex(bytes) : null); } - public static String hmac(Codec codec, byte[] plainText, byte[] secret, HMac hmac) throws NoSuchAlgorithmException, InvalidKeyException { + public static String hmac(HMac hmac, Codec codec, byte[] plainText, byte[] secret) throws NoSuchAlgorithmException, InvalidKeyException { Objects.requireNonNull(plainText); Objects.requireNonNull(secret); - Mac mac = Mac.getInstance(hmac.algo); - SecretKeySpec secretKeySpec = new SecretKeySpec(secret, hmac.algo); + Mac mac = Mac.getInstance(hmac.getAlgo()); + SecretKeySpec secretKeySpec = new SecretKeySpec(secret, hmac.getAlgo()); mac.init(secretKeySpec); return codec == Codec.BASE64 ? Base64.getEncoder().encodeToString(mac.doFinal(plainText)) : codec == Codec.HEX ? encodeHex(mac.doFinal(plainText)) : null; diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/HMac.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/HMac.java similarity index 60% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/util/HMac.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/security/HMac.java index 3c3e842..b0433f5 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/HMac.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/HMac.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.common.util; +package org.xbib.netty.http.common.security; public enum HMac { HMAC_SHA1("HMacSHA1"), @@ -9,4 +9,8 @@ public enum HMac { HMac(String algo) { this.algo = algo; } + + public String getAlgo() { + return algo; + } } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/SecurityUtil.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java similarity index 94% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/SecurityUtil.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java index a90edb2..c569e05 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/SecurityUtil.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java @@ -1,4 +1,4 @@ -package org.xbib.netty.http.common; +package org.xbib.netty.http.common.security; import io.netty.handler.codec.http2.Http2SecurityUtil; import io.netty.handler.ssl.CipherSuiteFilter; @@ -23,6 +23,9 @@ public class SecurityUtil { } } + private SecurityUtil() { + } + public interface Defaults { List OPENSSL_CIPHERS = Http2SecurityUtil.CIPHERS; diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/Codec.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/Codec.java deleted file mode 100644 index 1b3e92d..0000000 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/Codec.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.xbib.netty.http.common.util; - -public enum Codec { - BASE64, HEX -} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtils.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtil.java similarity index 97% rename from netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtils.java rename to netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtil.java index 8f7dd95..c07b535 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtils.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtil.java @@ -8,7 +8,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Locale; -public class DateTimeUtils { +public class DateTimeUtil { private static final ZoneId ZONE_UTC = ZoneId.of("UTC"); @@ -18,6 +18,9 @@ public class DateTimeUtils { private static final String ASCIITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy"; + private DateTimeUtil() { + } + public static String formatInstant(Instant instant) { return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)); } diff --git a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/CookieSignerTest.java b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/CookieSignerTest.java new file mode 100644 index 0000000..d068cd2 --- /dev/null +++ b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/CookieSignerTest.java @@ -0,0 +1,85 @@ +package org.xbib.netty.http.common.test.cookie; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.xbib.netty.http.common.cookie.DefaultCookie; +import org.xbib.netty.http.common.cookie.CookieSigner; +import org.xbib.netty.http.common.security.HMac; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CookieSignerTest { + + @Test + void testEncodeSignedCookie() { + String id = "dummy"; + CookieSigner cookieSigner = CookieSigner.builder().withHMac(HMac.HMAC_SHA256) + .withPublicValue(id) + .withPrivateValue(Base64.getEncoder().encodeToString("Hello".getBytes(StandardCharsets.UTF_8))) + .withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb") + .build(); + DefaultCookie cookie = new DefaultCookie("SESS", cookieSigner); + assertEquals("dummy", cookieSigner.getPublicValue()); + assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value()); + } + + @Test + void testDecodeSignedCookie() { + String rawValue = "dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D"; + CookieSigner cookieSigner = CookieSigner.builder() + .withHMac(HMac.HMAC_SHA256) + .withRawValue(rawValue) + .withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb") + .build(); + DefaultCookie cookie = new DefaultCookie("SESS", cookieSigner); + assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value()); + assertEquals("dummy", cookieSigner.getPublicValue()); + assertEquals("Hello", new String(Base64.getDecoder().decode(cookieSigner.getPrivateValue()), + StandardCharsets.UTF_8)); + } + + @Test + void testDecodeInvalidTuple() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String rawValue = "dummy%3JSGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D"; + CookieSigner.builder() + .withHMac(HMac.HMAC_SHA256) + .withRawValue(rawValue) + .withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb") + .build(); + + }); + } + + @Test + void testDecodeISOGivesMalformedInputException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String rawValue = "dummy%FCSGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D"; + CookieSigner.builder() + .withHMac(HMac.HMAC_SHA256) + .withRawValue(rawValue) + .withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb") + .build(); + + }); + } + + @Test + void testDecodeISO() { + String rawValue = "d%FCmmy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D"; + CookieSigner cookieSigner = CookieSigner.builder() + .withCharset(StandardCharsets.ISO_8859_1) + .withHMac(HMac.HMAC_SHA256) + .withRawValue(rawValue) + .withSecret("d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb") + .build(); + DefaultCookie cookie = new DefaultCookie("SESS", cookieSigner); + assertEquals("d%FCmmy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value()); + assertEquals("dümmy", cookieSigner.getPublicValue()); + assertEquals("Hello", new String(Base64.getDecoder().decode(cookieSigner.getPrivateValue()), + StandardCharsets.UTF_8)); + } +} diff --git a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/SignedCookieTest.java b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/SignedCookieTest.java deleted file mode 100644 index cad6062..0000000 --- a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/cookie/SignedCookieTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.xbib.netty.http.common.test.cookie; - -import org.junit.jupiter.api.Test; -import org.xbib.netty.http.common.cookie.Cookie; -import org.xbib.netty.http.common.cookie.DefaultCookie; -import org.xbib.netty.http.common.cookie.Payload; -import org.xbib.netty.http.common.util.Codec; -import org.xbib.netty.http.common.util.HMac; - -import java.nio.charset.MalformedInputException; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnmappableCharacterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.util.Base64; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class SignedCookieTest { - - @Test - void testEncodeDefaultCookie() { - Base64Codec codec = new Base64Codec(); - String cookieName = "SESS"; - String domain = ".hbz-nrw.de"; - String path = "/"; - String id = "dummy"; - Payload payload = new Payload(Codec.BASE64, HMac.HMAC_SHA256, - id, new String(codec.encode("Hello"), StandardCharsets.UTF_8), - "d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb"); - DefaultCookie cookie = new DefaultCookie(cookieName, payload); - cookie.setDomain(domain); - cookie.setPath(path); - cookie.setMaxAge(3600); - cookie.setHttpOnly(true); - cookie.setSecure(true); - cookie.setSameSite(Cookie.SameSite.LAX); - assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value()); - assertEquals(domain, cookie.domain()); - assertEquals(path, cookie.path()); - assertEquals(3600, cookie.maxAge()); - assertEquals(cookieName, cookie.name()); - assertTrue(cookie.isHttpOnly()); - assertTrue(cookie.isSecure()); - assertEquals(Cookie.SameSite.LAX, cookie.sameSite()); - } - - @Test - void testCookieValue() - throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { - Base64Codec codec = new Base64Codec(); - String rawCookieValue = "dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D"; - Payload payload = new Payload(Codec.BASE64, HMac.HMAC_SHA256, - rawCookieValue, "d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb"); - DefaultCookie cookie = new DefaultCookie("SESS", payload); - assertEquals("dummy", payload.getPublicValue()); - assertEquals("Hello", codec.decode(payload.getPrivateValue().getBytes(StandardCharsets.UTF_8))); - } - - class Base64Codec { - - byte[] encode(String payload) { - return Base64.getEncoder().encode(payload.getBytes(StandardCharsets.UTF_8)); - } - - String decode(byte[] bytes) { - return new String(Base64.getDecoder().decode(bytes), StandardCharsets.UTF_8); - } - } -} diff --git a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/CryptUtilsTest.java b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/security/CryptUtilTest.java similarity index 60% rename from netty-http-common/src/test/java/org/xbib/netty/http/common/test/CryptUtilsTest.java rename to netty-http-common/src/test/java/org/xbib/netty/http/common/test/security/CryptUtilTest.java index 7abf203..1248c9a 100644 --- a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/CryptUtilsTest.java +++ b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/security/CryptUtilTest.java @@ -1,33 +1,33 @@ -package org.xbib.netty.http.common.test; +package org.xbib.netty.http.common.test.security; import org.junit.jupiter.api.Test; -import org.xbib.netty.http.common.util.CryptUtils; +import org.xbib.netty.http.common.security.CryptUtil; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import static org.junit.jupiter.api.Assertions.assertEquals; -class CryptUtilsTest { +class CryptUtilTest { @Test void testRfc2307() throws NoSuchAlgorithmException { - assertEquals("{md5}ixqZU8RhEpaoJ6v4xHgE1w==", - CryptUtils.md5("Hello")); + CryptUtil.md5("Hello")); assertEquals("{sha}9/+ei3uy4Jtwk1pdeF4MxdnQq/A=", - CryptUtils.sha("Hello")); + CryptUtil.sha("Hello")); assertEquals("{sha256}GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=", - CryptUtils.sha256("Hello")); + CryptUtil.sha256("Hello")); assertEquals("{sha512}NhX4DJ0pPtdAJof5SyLVjlKbjMeRb4+sf933+9WvTPd309eVp6AKFr9+fz+5Vh7puq5IDan+ehh2nnGIawPzFQ==", - CryptUtils.sha512("Hello")); + CryptUtil.sha512("Hello")); } @Test void testHmac() throws InvalidKeyException, NoSuchAlgorithmException { assertEquals("Wgxn2SLeDKU+MGJQ5oWMH20sSUM=", - CryptUtils.hmacSHA1("hello", "world")); + CryptUtil.hmacSHA1(StandardCharsets.ISO_8859_1, "hello", "world")); assertEquals("PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=", - CryptUtils.hmacSHA256("hello", "world")); + CryptUtil.hmacSHA256(StandardCharsets.ISO_8859_1, "hello", "world")); } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java index 3901c66..bd955ed 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Domain.java @@ -8,7 +8,7 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import org.xbib.netty.http.common.HttpAddress; -import org.xbib.netty.http.common.SecurityUtil; +import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.server.endpoint.HttpEndpoint; import org.xbib.netty.http.server.endpoint.HttpEndpointResolver; import org.xbib.netty.http.server.endpoint.service.Service; @@ -147,7 +147,7 @@ public class Domain { this.serverName = serverName; this.aliases = new LinkedHashSet<>(); this.httpEndpointResolvers = new ArrayList<>(); - this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY; // InsecureTrustManagerFactory.INSTANCE; + this.trustManagerFactory = SecurityUtil.Defaults.DEFAULT_TRUST_MANAGER_FACTORY; this.sslProvider = SecurityUtil.Defaults.DEFAULT_SSL_PROVIDER; this.ciphers = SecurityUtil.Defaults.DEFAULT_CIPHERS; this.cipherSuiteFilter = SecurityUtil.Defaults.DEFAULT_CIPHER_SUITE_FILTER; diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java index 8714c7b..c408fb5 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java @@ -22,7 +22,7 @@ import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.NetworkUtils; import org.xbib.netty.http.server.handler.http.HttpChannelInitializer; import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer; -import org.xbib.netty.http.common.SecurityUtil; +import org.xbib.netty.http.common.security.SecurityUtil; import org.xbib.netty.http.server.transport.HttpTransport; import org.xbib.netty.http.server.transport.Http2Transport; import org.xbib.netty.http.server.transport.Transport; @@ -83,8 +83,7 @@ public final class Server { Class socketChannelClass) { Objects.requireNonNull(serverConfig); this.serverConfig = serverConfig; - this.byteBufAllocator = byteBufAllocator != null ? - byteBufAllocator : ByteBufAllocator.DEFAULT; + this.byteBufAllocator = byteBufAllocator != null ? byteBufAllocator : ByteBufAllocator.DEFAULT; this.parentEventLoopGroup = createParentEventLoopGroup(serverConfig, parentEventLoopGroup); this.childEventLoopGroup = createChildEventLoopGroup(serverConfig, childEventLoopGroup); this.socketChannelClass = createSocketChannelClass(serverConfig, socketChannelClass); @@ -191,6 +190,7 @@ public final class Server { parentEventLoopGroup.shutdownGracefully(); try { if (channelFuture != null) { + // close channel and wait channelFuture.channel().closeFuture().sync(); } } catch (InterruptedException e) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/cookie/ServerCookieEncoder.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/cookie/ServerCookieEncoder.java index b91fe46..f2d1138 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/cookie/ServerCookieEncoder.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/cookie/ServerCookieEncoder.java @@ -5,7 +5,7 @@ import org.xbib.netty.http.common.cookie.CookieEncoder; import org.xbib.netty.http.common.cookie.CookieHeaderNames; import org.xbib.netty.http.common.cookie.CookieUtil; import org.xbib.netty.http.common.cookie.DefaultCookie; -import org.xbib.netty.http.common.util.DateTimeUtils; +import org.xbib.netty.http.common.util.DateTimeUtil; import java.time.Instant; import java.util.ArrayList; @@ -86,7 +86,7 @@ public final class ServerCookieEncoder extends CookieEncoder { Instant expires = Instant.ofEpochMilli(cookie.maxAge() * 1000 + System.currentTimeMillis()); buf.append(CookieHeaderNames.EXPIRES); buf.append(CookieUtil.EQUALS); - buf.append(DateTimeUtils.formatMillis(expires.toEpochMilli())); + buf.append(DateTimeUtil.formatMillis(expires.toEpochMilli())); buf.append(CookieUtil.SEMICOLON); buf.append(CookieUtil.SP); } @@ -110,28 +110,6 @@ public final class ServerCookieEncoder extends CookieEncoder { return CookieUtil.stripTrailingSeparator(buf); } - /** - * Deduplicate a list of encoded cookies by keeping only the last instance with a given name. - * - * @param encoded The list of encoded cookies. - * @param nameToLastIndex A map from cookie name to index of last cookie instance. - * @return The encoded list with all but the last instance of a named cookie. - */ - private static List dedup(List encoded, Map nameToLastIndex) { - boolean[] isLastInstance = new boolean[encoded.size()]; - for (int idx : nameToLastIndex.values()) { - isLastInstance[idx] = true; - } - List dedupd = new ArrayList<>(nameToLastIndex.size()); - int n = encoded.size(); - for (int i = 0; i < n; i++) { - if (isLastInstance[i]) { - dedupd.add(encoded.get(i)); - } - } - return dedupd; - } - /** * Batch encodes cookies into Set-Cookie header values. * @@ -204,4 +182,26 @@ public final class ServerCookieEncoder extends CookieEncoder { } return hasDupdName ? dedup(encoded, nameToIndex) : encoded; } + + /** + * Deduplicate a list of encoded cookies by keeping only the last instance with a given name. + * + * @param encoded The list of encoded cookies. + * @param nameToLastIndex A map from cookie name to index of last cookie instance. + * @return The encoded list with all but the last instance of a named cookie. + */ + private static List dedup(List encoded, Map nameToLastIndex) { + boolean[] isLastInstance = new boolean[encoded.size()]; + for (int idx : nameToLastIndex.values()) { + isLastInstance[idx] = true; + } + List dedupd = new ArrayList<>(nameToLastIndex.size()); + int n = encoded.size(); + for (int i = 0; i < n; i++) { + if (isLastInstance[i]) { + dedupd.add(encoded.get(i)); + } + } + return dedupd; + } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java index 713f246..75b6855 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java @@ -32,7 +32,7 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable endpoint.matches(httpEndpointDescriptor)) - .sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath())).collect(Collectors.toList())); + .sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getPath())) + .collect(Collectors.toList())); List matchingEndpoints = endpointDescriptors.get(httpEndpointDescriptor); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, () -> "endpoint = " + httpEndpointDescriptor + @@ -83,7 +83,7 @@ public class HttpEndpointResolver { return endpointDescriptors; } - protected HttpEndpoint createDefaultEndpoint() { + private HttpEndpoint createDefaultEndpoint() { return HttpEndpoint.builder() .setPath("/**") .addMethod("GET") @@ -94,27 +94,6 @@ public class HttpEndpointResolver { }).build(); } - /** - * A simple LRU cache, based on a {@link LinkedHashMap}. - * - * @param the key type parameter - * @param the vale type parameter - */ - @SuppressWarnings("serial") - private static class LRUCache extends LinkedHashMap { - - private final int cacheSize; - - LRUCache(int cacheSize) { - super(16, 0.75f, true); - this.cacheSize = cacheSize; - } - - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > cacheSize; - } - } - public static Builder builder() { return new Builder(); } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java index 4c491c6..bb2a792 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ResourceService.java @@ -6,7 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.stream.ChunkedNioStream; -import org.xbib.netty.http.common.util.DateTimeUtils; +import org.xbib.netty.http.common.util.DateTimeUtil; import org.xbib.netty.http.server.ServerRequest; import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.util.MimeTypeUtils; @@ -55,14 +55,14 @@ public abstract class ResourceService implements Service { long maxAgeSeconds = 24 * 3600; long expirationMillis = System.currentTimeMillis() + 1000 * maxAgeSeconds; if (isCacheResponseEnabled()) { - serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis)) + serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatMillis(expirationMillis)) .withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + maxAgeSeconds); } boolean sent = false; if (isETagResponseEnabled()) { Instant lastModifiedInstant = resource.getLastModified(); String eTag = resource.getResourcePath().hashCode() + "/" + lastModifiedInstant.toEpochMilli() + "/" + resource.getLength(); - Instant ifUnmodifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE)); + Instant ifUnmodifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE)); if (ifUnmodifiedSinceInstant != null && ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) { ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED); @@ -76,20 +76,20 @@ public abstract class ResourceService implements Service { String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH); if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) { serverResponse.withHeader(HttpHeaderNames.ETAG, eTag) - .withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis)); + .withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatMillis(expirationMillis)); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED); return; } - Instant ifModifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE)); + Instant ifModifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE)); if (ifModifiedSinceInstant != null && ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) { serverResponse.withHeader(HttpHeaderNames.ETAG, eTag) - .withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis)); + .withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatMillis(expirationMillis)); ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED); return; } serverResponse.withHeader(HttpHeaderNames.ETAG, eTag) - .withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtils.formatInstant(lastModifiedInstant)); + .withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatInstant(lastModifiedInstant)); if (isRangeResponseEnabled()) { performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers); sent = true; @@ -119,7 +119,7 @@ public abstract class ResourceService implements Service { String ifRange = headers.get(HttpHeaderNames.IF_RANGE); if (ifRange != null && !ifRange.equals(eTag)) { try { - Instant ifRangeTime = DateTimeUtils.parseDate(ifRange); + Instant ifRangeTime = DateTimeUtil.parseDate(ifRange); if (ifRangeTime != null && ifRangeTime.plusMillis(1000).isBefore(resource.getLastModified())) { ranges.add(full); } diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/cookie/ServerCookieEncoderTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/cookie/ServerCookieEncoderTest.java index 1643acd..835c033 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/cookie/ServerCookieEncoderTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/cookie/ServerCookieEncoderTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.xbib.netty.http.client.cookie.ClientCookieEncoder; import org.xbib.netty.http.common.cookie.Cookie; import org.xbib.netty.http.common.cookie.DefaultCookie; -import org.xbib.netty.http.common.util.DateTimeUtils; +import org.xbib.netty.http.common.util.DateTimeUtil; import org.xbib.netty.http.server.cookie.ServerCookieEncoder; import java.time.Instant; @@ -35,7 +35,7 @@ class ServerCookieEncoderTest { String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie); Matcher matcher = Pattern.compile(result).matcher(encodedCookie); assertTrue(matcher.find()); - Instant expire = DateTimeUtils.parseDate(matcher.group(1)); + Instant expire = DateTimeUtil.parseDate(matcher.group(1)); long diff = (expire.toEpochMilli() - System.currentTimeMillis()) / 1000; assertTrue(Math.abs(diff - maxAge) <= 2); }